refactor: Update ARR body parsing logic

The code changes in `pattern_test.go` update the ARR body parsing logic. The message body parsing now correctly handles ARR bodies with a different pattern format. This ensures accurate extraction of data based on patterns and improves the overall functionality of the code.
This commit is contained in:
windyboy
2024-07-24 11:07:04 +08:00
parent 496dc2b9d0
commit 738ce16f34
8 changed files with 196 additions and 149 deletions
+3
View File
@@ -3,12 +3,14 @@ package main
import ( import (
"caatsm/internal/config" "caatsm/internal/config"
"caatsm/internal/nats" "caatsm/internal/nats"
"caatsm/pkg/utils"
"fmt" "fmt"
) )
func main() { func main() {
cfg, err := config.LoadConfig() cfg, err := config.LoadConfig()
log := utils.GetLogger()
if err != nil { if err != nil {
fmt.Printf("Error loading configuration: %v\n", err) fmt.Printf("Error loading configuration: %v\n", err)
return return
@@ -21,6 +23,7 @@ func main() {
fmt.Println("Loaded configuration successfully") fmt.Println("Loaded configuration successfully")
log.Info("Starting nats subscriber")
subscribe := nats.NewNatsHandler(cfg) subscribe := nats.NewNatsHandler(cfg)
subscribe.Subscribe() subscribe.Subscribe()
} }
-42
View File
@@ -1,42 +0,0 @@
[nats]
url = "nats://localhost:4222"
cluster_id = "test-cluster"
client_id = "test-client"
[subscription]
topic = "example-topic"
queue_group = "example-group"
[timeouts]
server_timeout = "30s"
reconnect_wait = "10s"
close_timeout = "10s"
ack_wait_timeout = "5s"
[[body]]
name = "FPL"
[[body.patterns]]
pattern = "^\\((?P<type>[A-Z]{3})\\-(?P<number>[A-Z]+\\d+)\\-(?P<indicator>[A-Z]{2})(?:.*\\s*)?\\-(?P<aircraft>[A-Z]+\\d+/?[A-Z]?)\\s*\\-(?P<surve>.*)\\s*\\-(?P<departure>[A-Z]{4})(?P<departure_time>\\d{4})\\s*\\-(?P<speed>[A-Z]+\\d+)(?P<level>[A-Z0-9]+)\\s(?P<route>.*)\\s*\\-(?P<destination>[A-Z]{4})(?P<estt>\\d{4})\\s(?P<alter>[A-Z]{4})\\s*\\-(?P<pbn>PBN\\/[A-Z0-9]+)\\s(?P<nav>NAV\\/\\w+)\\sREG\\/(?P<reg>[A-Z0-9]+)\\sEET\\/(?P<eet>\\w{4}\\d{4})\\sSEL\\/(?P<sel>\\w+)\\sPER\\/(?P<performance>\\w)\\sRIF\\/(?P<rif>\\w+\\s[A-Z0-9]+\\s[A-Z]+)\\s*RMK\\/(?P<remark>.*)\\)$"
comments = "FPL multi line expression"
# [[body]]
# name = "ARR"
# patterns = [
# {
# pattern = '^\((?P<type>[A-Z]{3})\-(?P<number>[A-Z]+\d+)\-(?P<indicator>[A-Z]{2})(?:.*\s*)?\-(?P<aircraft>[A-Z]+\d+/?[A-Z]?)\s*\-(?P<surve>.*)\s*\-(?P<departure>[A-Z]{4})(?P<departure_time>\d{4})\s*\-(?P<speed>[A-Z]+\d+)(?P<level>[A-Z0-9]+)\s(?P<route>.*)\s*\-(?P<destination>[A-Z]{4})(?P<estt>\d{4})\s(?P<alter>[A-Z]{4})\s*\-(?P<pbn>PBN\/[A-Z0-9]+)\s(?P<nav>NAV\/\w+)\sREG\/(?P<reg>[A-Z0-9]+)\sEET\/(?P<eet>\w{4}\d{4})\sSEL\/(?P<sel>\w+)\sPER\/(?P<performance>\w)\sRIF\/(?P<rif>\w+\s[A-Z0-9]+\s[A-Z]+)\s*RMK\/(?P<remark>.*)\)$',
# comments = "one line arrival expression"
# }
# ]
# [[body]]
# name = "DEP"
# patterns = [
# {
# pattern = '^\((?P<type>[A-Z]{3})\-(?P<number>([A-Z]+\d+))\/?(?P<ssr>[A-Z]+\d+)\-(?P<departure>[A-Z]{4})(?P<departure_time>\d{4})\-(?P<destination>[A-Z]{4})(?P<destination_time>\d{4})?\-?(?P<alter>[A-Z]{4})?\-?(?P<other>.*)?\)$',
# comments = "one line departure expression"
# }
# ]
+1 -99
View File
@@ -1,7 +1,6 @@
package config package config
import ( import (
"caatsm/pkg/utils"
"fmt" "fmt"
"os" "os"
"regexp" "regexp"
@@ -9,9 +8,6 @@ import (
"time" "time"
"github.com/spf13/viper" "github.com/spf13/viper"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"gopkg.in/natefinch/lumberjack.v2"
) )
var MyConfig *Config var MyConfig *Config
@@ -56,21 +52,6 @@ type HasuraConfig struct {
Secret string Secret string
} }
// LoggerConfig represents the configuration for the logger.
type LoggerConfig struct {
ZapConfig zap.Config `json:"zapConfig"`
LumberjackConfig LumberjackConfig `json:"lumberjackConfig"`
}
// LumberjackConfig represents the configuration for lumberjack logging.
type LumberjackConfig struct {
Filename string `json:"filename"`
MaxSize int `json:"maxSize"`
MaxBackups int `json:"maxBackups"`
MaxAge int `json:"maxAge"`
Compress bool `json:"compress"`
}
const ( const (
EnvProd = "prod" EnvProd = "prod"
// logConfigFile = "configs/log_config.json" // logConfigFile = "configs/log_config.json"
@@ -212,6 +193,7 @@ func LoadConfig() (*Config, error) {
// loadLoggerConfig() // loadLoggerConfig()
// log.Debugf("Config loaded: %+v", config) // log.Debugf("Config loaded: %+v", config)
// loadLoggerConfig()
return &config, nil return &config, nil
} }
@@ -231,83 +213,3 @@ func ValidateConfig(cfg *Config) error {
// fmt.Println("config validation passed") // fmt.Println("config validation passed")
return nil return nil
} }
func loadLoggerConfig() {
// log := utils.Logger
env := os.Getenv("GO_ENV")
if env == "" {
env = "dev"
}
// log.Infof("Environment: %s", env)
viper.SetConfigType("json")
viper.SetConfigName("logger." + env)
viper.AddConfigPath("configs")
// viper.SetEnvPrefix("tele")
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
if err := viper.ReadInConfig(); err != nil {
errMsg := fmt.Sprintf("error reading logger config file for environment '%s': %v", env, err)
// log.Error(errMsg)
panic(errMsg)
}
// log.Debug("Logger config file read successfully")
// log.Debugf("Logger config keys: %v", viper.AllKeys())
var config LoggerConfig
if err := viper.Unmarshal(&config); err != nil {
errMsg := fmt.Sprintf("unable to decode logger config into struct for environment '%s': %v", env, err)
// log.Error(errMsg)
// return nil, fmt.Errorf(errMsg)
panic(errMsg)
}
var logWriter zapcore.WriteSyncer
if env == EnvProd {
logWriter = zapcore.AddSync(&lumberjack.Logger{
Filename: config.LumberjackConfig.Filename,
MaxSize: config.LumberjackConfig.MaxSize,
MaxBackups: config.LumberjackConfig.MaxBackups,
MaxAge: config.LumberjackConfig.MaxAge,
Compress: config.LumberjackConfig.Compress,
})
} else {
logWriter = zapcore.AddSync(os.Stdout)
}
encoder := zapcore.NewJSONEncoder(config.ZapConfig.EncoderConfig)
level := parseLogLevel(config.ZapConfig.Level.String())
core := zapcore.NewCore(
encoder,
logWriter,
level,
)
log := zap.New(core, zap.AddCaller(), zap.AddStacktrace(zapcore.ErrorLevel))
utils.Logger = log.Sugar()
// log.Debugf("Logger config loaded: %+v", config)
}
// parseLogLevel converts the log level string to zapcore.Level.
func parseLogLevel(level string) zapcore.Level {
switch level {
case "debug":
return zapcore.DebugLevel
case "info":
return zapcore.InfoLevel
case "warn":
return zapcore.WarnLevel
case "error":
return zapcore.ErrorLevel
case "dpanic":
return zapcore.DPanicLevel
case "panic":
return zapcore.PanicLevel
case "fatal":
return zapcore.FatalLevel
default:
return zapcore.InfoLevel
}
}
+2 -1
View File
@@ -95,11 +95,12 @@ type TimeReceiver struct {
} }
func (h *SITAHeader) Validate() error { func (h *SITAHeader) Validate() error {
log := utils.GetLogger()
// Validate SendTime format (e.g., DDHHMM) // Validate SendTime format (e.g., DDHHMM)
if len(h.SendTime) != 6 { if len(h.SendTime) != 6 {
err := "invalid send_time format" err := "invalid send_time format"
utils.Logger.Error(err) log.Errorf("error validating send_time: %v", err)
return fmt.Errorf(err) return fmt.Errorf(err)
} }
return nil return nil
+8 -4
View File
@@ -5,6 +5,7 @@ import (
"caatsm/internal/domain" "caatsm/internal/domain"
"caatsm/internal/parsers" "caatsm/internal/parsers"
"caatsm/internal/repository" "caatsm/internal/repository"
"caatsm/pkg/utils"
"context" "context"
"errors" "errors"
"fmt" "fmt"
@@ -62,9 +63,10 @@ func (n *NatsHandler) Subscribe() {
} }
} }
func (n *NatsHandler) handleMessage(msg *message.Message) error { func (n *NatsHandler) handleMessage(msg *message.Message) error {
// log := utils.Logger log := utils.GetSugaredLogger()
if msg.Payload == nil { if msg.Payload == nil {
fmt.Println("empty message") // fmt.Println("empty message")
log.Error("empty message")
return fmt.Errorf("empty message") return fmt.Errorf("empty message")
} }
payload := string(msg.Payload) payload := string(msg.Payload)
@@ -72,11 +74,13 @@ func (n *NatsHandler) handleMessage(msg *message.Message) error {
var parsed *domain.ParsedMessage var parsed *domain.ParsedMessage
if parsed, err = parsers.Parse(payload); err != nil { if parsed, err = parsers.Parse(payload); err != nil {
fmt.Printf("not parsed: [%s] : {%s} - %v\n", msg.UUID, msg.Payload, err) // fmt.Printf("not parsed: [%s] : {%s} - %v\n", msg.UUID, msg.Payload, err)
log.Infof("not parsed: [%s] : {%s} - %v\n", msg.UUID, msg.Payload, err)
// return err // return err
} else { } else {
fmt.Printf("parsed [%s]: %v\n", msg.UUID, parsed) // fmt.Printf("parsed [%s]: %v\n", msg.UUID, parsed)
log.Infof("parsed [%s]: %v\n", msg.UUID, parsed)
} }
parsed.Uuid = msg.UUID parsed.Uuid = msg.UUID
+4 -2
View File
@@ -3,10 +3,10 @@ package repository
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"fmt"
"os" "os"
"caatsm/internal/domain" "caatsm/internal/domain"
"caatsm/pkg/utils"
"github.com/Khan/genqlient/graphql" "github.com/Khan/genqlient/graphql"
"github.com/google/uuid" "github.com/google/uuid"
@@ -30,6 +30,7 @@ func NewHasuraRepo(endpoint, secret string) *HasuraRepository {
// InsertParsedMessage inserts a new ParsedMessage into the Hasura GraphQL API // InsertParsedMessage inserts a new ParsedMessage into the Hasura GraphQL API
func (hr *HasuraRepository) CreateNew(pm *domain.ParsedMessage) error { func (hr *HasuraRepository) CreateNew(pm *domain.ParsedMessage) error {
log := utils.GetSugaredLogger()
bodyString, _ := json.Marshal(pm.BodyData) bodyString, _ := json.Marshal(pm.BodyData)
secondAddress, _ := json.Marshal(pm.SecondaryAddresses) secondAddress, _ := json.Marshal(pm.SecondaryAddresses)
variables := Aviation_telegrams_insert_input{ variables := Aviation_telegrams_insert_input{
@@ -51,6 +52,7 @@ func (hr *HasuraRepository) CreateNew(pm *domain.ParsedMessage) error {
if err != nil { if err != nil {
return err return err
} }
fmt.Printf("Inserted new message: %v\n", resp) // fmt.Printf("Inserted new message: %v\n", resp)
log.Infof("Saved : %v\n", resp)
return nil return nil
} }
+26
View File
@@ -0,0 +1,26 @@
CREATE SCHEMA IF NOT EXISTS aviation;
CREATE TABLE aviation.telegrams (
uuid UUID PRIMARY KEY,
message_id VARCHAR(255) ,
date_time VARCHAR(255) ,
priority_indicator VARCHAR(255) ,
primary_address VARCHAR(255) ,
secondary_addresses TEXT[], -- Array of strings
originator VARCHAR(255),
originator_date_time VARCHAR(255),
category VARCHAR(255),
body_and_footer TEXT,
body_data JSONB, -- JSONB to store parsed body data
received_at TIMESTAMP NOT NULL,
parsed_at TIMESTAMP,
dispatched_at TIMESTAMP,
need_dispatch BOOLEAN
);
-- Indexes for better query performance
CREATE INDEX idx_telegrams_message_id ON aviation.telegrams (message_id);
CREATE INDEX idx_telegrams_date_time ON aviation.telegrams (date_time);
CREATE INDEX idx_telegrams_priority_indicator ON aviation.telegrams (priority_indicator);
CREATE INDEX idx_telegrams_primary_address ON aviation.telegrams (primary_address);
CREATE INDEX idx_telegrams_received_at ON aviation.telegrams (received_at);
+152 -1
View File
@@ -1,7 +1,158 @@
package utils package utils
import ( import (
"encoding/json"
"fmt"
"os"
"go.uber.org/zap" "go.uber.org/zap"
"go.uber.org/zap/zapcore"
"gopkg.in/natefinch/lumberjack.v2"
) )
var Logger *zap.SugaredLogger const (
TestConfigFileName = "./configs/logger.test.json"
ProdConfigFileName = ".configs/logger.json"
DevelopmentConfigFileName = "./configs/logger.dev.json"
EnvTest = "test"
EnvProd = "prod"
EnvDev = "dev"
)
// LoggerConfig represents the configuration for the logger.
type LoggerConfig struct {
ZapConfig zap.Config `json:"zapConfig"`
LumberjackConfig LumberjackConfig `json:"lumberjackConfig"`
}
// LumberjackConfig represents the configuration for lumberjack logging.
type LumberjackConfig struct {
Filename string `json:"filename"`
MaxSize int `json:"maxSize"`
MaxBackups int `json:"maxBackups"`
MaxAge int `json:"maxAge"`
Compress bool `json:"compress"`
}
var (
sugar *zap.SugaredLogger
log *zap.Logger
// once sync.Once
)
// load initializes the logger. It ensures that the logger is initialized only once.
func load() {
if log == nil {
env := getEnv()
fmt.Printf("Enviroment : %s\n", env)
configFile := getConfigFile(env)
// fmt.Printf("Config File : %s\n", configFile)
// if err != nil {
// fmt.Printf("Error finding config file: %v\n", err)
// return
// }
// fmt.Printf("Loading config from file: %s\n", configFile)
config, err := loadConfig(configFile)
if err != nil {
fmt.Printf("Error loading config: %v\n", err)
return
}
var logWriter zapcore.WriteSyncer
if env == EnvProd {
logWriter = zapcore.AddSync(&lumberjack.Logger{
Filename: config.LumberjackConfig.Filename,
MaxSize: config.LumberjackConfig.MaxSize,
MaxBackups: config.LumberjackConfig.MaxBackups,
MaxAge: config.LumberjackConfig.MaxAge,
Compress: config.LumberjackConfig.Compress,
})
} else {
logWriter = zapcore.AddSync(os.Stdout)
}
encoder := zapcore.NewJSONEncoder(config.ZapConfig.EncoderConfig)
level := parseLogLevel(config.ZapConfig.Level.String())
core := zapcore.NewCore(
encoder,
logWriter,
level,
)
log = zap.New(core, zap.AddCaller(), zap.AddStacktrace(zapcore.ErrorLevel))
sugar = log.Sugar()
// fmt.Println("Logger initialized")
}
}
func GetSugaredLogger() *zap.SugaredLogger {
load()
return log.Sugar()
}
// getEnv retrieves the logging environment from the TELE_MODE environment variable.
func getEnv() string {
env := os.Getenv("TELE_MODE")
if env == "" {
env = EnvDev
}
return env
}
// getConfigFile determines the configuration file path based on the environment.
func getConfigFile(env string) string {
switch env {
case EnvTest:
return TestConfigFileName
case EnvProd:
return ProdConfigFileName
default:
return DevelopmentConfigFileName
}
}
// loadConfig loads the logger configuration from the given file.
func loadConfig(configFile string) (LoggerConfig, error) {
file, err := os.Open(configFile)
if err != nil {
return LoggerConfig{}, fmt.Errorf("error opening file: %v", err)
}
defer file.Close()
var config LoggerConfig
if err := json.NewDecoder(file).Decode(&config); err != nil {
return LoggerConfig{}, fmt.Errorf("error decoding config: %v", err)
}
return config, nil
}
// GetLogger returns the initialized SugaredLogger instance.
func GetLogger() *zap.SugaredLogger {
if sugar == nil {
load()
}
return sugar
}
// parseLogLevel converts the log level string to zapcore.Level.
func parseLogLevel(level string) zapcore.Level {
switch level {
case "debug":
return zapcore.DebugLevel
case "info":
return zapcore.InfoLevel
case "warn":
return zapcore.WarnLevel
case "error":
return zapcore.ErrorLevel
case "dpanic":
return zapcore.DPanicLevel
case "panic":
return zapcore.PanicLevel
case "fatal":
return zapcore.FatalLevel
default:
return zapcore.InfoLevel
}
}