Initial add backend stuff
This commit is contained in:
41
l4g/database.go
Normal file
41
l4g/database.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package l4g
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
// dbWriter is the app-supplied sink that persists a log Entry to the database.
|
||||
// The framework cannot import the app's repository, so the app registers a
|
||||
// writer at startup via SetDatabaseWriter. If none is registered, the database
|
||||
// logger falls back to the terminal so log lines are never silently dropped.
|
||||
var dbWriter func(Entry) error
|
||||
|
||||
// SetDatabaseWriter registers the function the database logger uses to persist
|
||||
// entries. Apps typically wire it to their repository:
|
||||
//
|
||||
// l4g.SetDatabaseWriter(func(e l4g.Entry) error {
|
||||
// return repository.InsertLogEntry(context.Background(), models.LogEntry(e))
|
||||
// })
|
||||
func SetDatabaseWriter(w func(Entry) error) { dbWriter = w }
|
||||
|
||||
type DatabaseLogger struct{}
|
||||
|
||||
func NewDatabaseLogger() *DatabaseLogger {
|
||||
return &DatabaseLogger{}
|
||||
}
|
||||
|
||||
func (d *DatabaseLogger) Write(entry Entry) error {
|
||||
if dbWriter == nil {
|
||||
return NewTerminalLogger().Write(entry)
|
||||
}
|
||||
return dbWriter(entry)
|
||||
}
|
||||
|
||||
func (d *DatabaseLogger) Fatal(entry Entry) error {
|
||||
err := d.Write(entry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
os.Exit(1)
|
||||
return nil
|
||||
}
|
||||
51
l4g/debug.go
Normal file
51
l4g/debug.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package l4g
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
var debugLogger *log.Logger
|
||||
|
||||
func init() {
|
||||
debugLogger = log.New(os.Stdout, "[DEBUG] ", log.LstdFlags)
|
||||
}
|
||||
|
||||
func Debug(v ...any) {
|
||||
debugLogger.Print(v...)
|
||||
}
|
||||
|
||||
func Debugf(format string, v ...any) {
|
||||
debugLogger.Printf(format, v...)
|
||||
}
|
||||
|
||||
func Debugln(v ...any) {
|
||||
debugLogger.Println(v...)
|
||||
}
|
||||
|
||||
func DebugFatal(v ...any) {
|
||||
debugLogger.Print(v...)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func DebugFatalf(format string, v ...any) {
|
||||
debugLogger.Printf(format, v...)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func DebugFatalln(v ...any) {
|
||||
debugLogger.Println(v...)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func DebugInit(message string) {
|
||||
timestamp := time.Now().Format("15:04:05")
|
||||
fmt.Printf("[%s] INIT: %s\n", timestamp, message)
|
||||
}
|
||||
|
||||
func DebugServer(message string) {
|
||||
timestamp := time.Now().Format("15:04:05")
|
||||
fmt.Printf("[%s] SERVER: %s\n", timestamp, message)
|
||||
}
|
||||
66
l4g/doc.go
Normal file
66
l4g/doc.go
Normal file
@@ -0,0 +1,66 @@
|
||||
// Package l4g provides logging functionality for the application.
|
||||
//
|
||||
// This package implements two separate logging systems:
|
||||
//
|
||||
// # MAIN LOGGER
|
||||
//
|
||||
// The main logger is configurable via the LOGGER_TYPE environment variable
|
||||
// and is used for structured application logging:
|
||||
//
|
||||
// - LOGGER_TYPE="terminal" - Outputs to stdout with formatted messages
|
||||
// - LOGGER_TYPE="file" - Writes to a log file (default: ./app.log)
|
||||
// - LOGGER_TYPE="database" - Stores structured log entries in the database
|
||||
//
|
||||
// Use the main logger for:
|
||||
// - User activities and business logic events
|
||||
// - Error logging and exception handling
|
||||
// - Audit trails and security events
|
||||
// - System state changes
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// l4g.Init(config.GetConfig().LoggerType)
|
||||
// entry := model.LogEntry{
|
||||
// LogType: l4g.LOG_TYPE_USER,
|
||||
// Severity: l4g.SEVERITY_INFO,
|
||||
// Content: &message,
|
||||
// }
|
||||
// l4g.Write(entry)
|
||||
//
|
||||
// # DEBUG LOGGER
|
||||
//
|
||||
// The debug logger is completely separate and always outputs to terminal,
|
||||
// regardless of the main logger configuration. It's used for development
|
||||
// and initialization messages that should always be visible:
|
||||
//
|
||||
// - Program initialization and startup messages
|
||||
// - Development debugging and troubleshooting
|
||||
// - Server configuration and status information
|
||||
//
|
||||
// Use the debug logger for:
|
||||
// - Package initialization messages
|
||||
// - Server startup and configuration
|
||||
// - Development debugging (temporary print statements)
|
||||
// - System health checks during startup
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// l4g.DebugInit("Initializing database connection")
|
||||
// l4g.DebugServer("Server listening on port 8080")
|
||||
// l4g.Debug("Temporary debug message")
|
||||
//
|
||||
// # LOG TYPES AND SEVERITY LEVELS
|
||||
//
|
||||
// Log Types:
|
||||
// - LOG_TYPE_SYSTEM: System-level events and operations
|
||||
// - LOG_TYPE_USER: User activities and interactions
|
||||
// - LOG_TYPE_ORG: Organization-level events
|
||||
// - LOG_TYPE_AUDIT: Security and compliance events
|
||||
//
|
||||
// Severity Levels:
|
||||
// - SEVERITY_DEBUG: Development and troubleshooting information
|
||||
// - SEVERITY_INFO: General informational messages
|
||||
// - TYPE_WARN: Warning conditions that should be noted
|
||||
// - SEVERITY_ERROR: Error conditions that affect functionality
|
||||
// - SEVERITY_FATAL: Critical errors that cause program termination
|
||||
package l4g
|
||||
23
l4g/entry.go
Normal file
23
l4g/entry.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package l4g
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// Entry is a single structured log record. It is the framework-owned mirror of
|
||||
// the application's log-entry model: field names, types, and order match, so an
|
||||
// app can convert directly (e.g. models.LogEntry(entry)) when persisting via the
|
||||
// database writer registered with SetDatabaseWriter.
|
||||
type Entry struct {
|
||||
ID uuid.UUID
|
||||
AppUserID *uuid.UUID
|
||||
OrgID *uuid.UUID
|
||||
Content *string
|
||||
StructuredContent *string
|
||||
Category int32
|
||||
LogType int32
|
||||
Timestamp time.Time
|
||||
IdentityID *uuid.UUID
|
||||
}
|
||||
109
l4g/file.go
Normal file
109
l4g/file.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package l4g
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
type FileLogger struct {
|
||||
filePath string
|
||||
}
|
||||
|
||||
func NewFileLogger(filePath string) *FileLogger {
|
||||
return &FileLogger{filePath: filePath}
|
||||
}
|
||||
|
||||
func (f *FileLogger) Write(entry Entry) error {
|
||||
err := f.ensureLogFileExists()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
file, err := os.OpenFile(f.filePath, os.O_APPEND|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open log file: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
timestamp := entry.Timestamp.Format(time.RFC3339)
|
||||
|
||||
var category string
|
||||
switch entry.Category {
|
||||
case CATEGORY_SYSTEM:
|
||||
category = "SYSTEM"
|
||||
case CATEGORY_ADMIN:
|
||||
category = "ADMIN"
|
||||
case CATEGORY_USER:
|
||||
category = "USER"
|
||||
case CATEGORY_ORG:
|
||||
category = "ORG"
|
||||
case CATEGORY_AUDIT:
|
||||
category = "AUDIT"
|
||||
case CATEGORY_AUTH:
|
||||
category = "AUTH"
|
||||
default:
|
||||
category = "UNKNOWN"
|
||||
}
|
||||
|
||||
var logType string
|
||||
switch entry.LogType {
|
||||
case TYPE_DEBUG:
|
||||
logType = "DEBUG"
|
||||
case TYPE_INFO:
|
||||
logType = "INFO"
|
||||
case TYPE_WARN:
|
||||
logType = "WARN"
|
||||
case TYPE_ERROR:
|
||||
logType = "ERROR"
|
||||
default:
|
||||
logType = "UNKNOWN"
|
||||
}
|
||||
|
||||
content := ""
|
||||
if entry.Content != nil {
|
||||
content = *entry.Content
|
||||
}
|
||||
|
||||
logLine := fmt.Sprintf("[%s] %s/%s: %s", timestamp, category, logType, content)
|
||||
|
||||
if entry.StructuredContent != nil {
|
||||
logLine += fmt.Sprintf(" | Details: %s", *entry.StructuredContent)
|
||||
}
|
||||
|
||||
logLine += "\n"
|
||||
|
||||
_, err = file.WriteString(logLine)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write to log file: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FileLogger) Fatal(entry Entry) error {
|
||||
err := f.Write(entry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
os.Exit(1)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FileLogger) ensureLogFileExists() error {
|
||||
dir := filepath.Dir(f.filePath)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create log directory: %w", err)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(f.filePath); os.IsNotExist(err) {
|
||||
file, err := os.Create(f.filePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create log file: %w", err)
|
||||
}
|
||||
file.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
84
l4g/logger.go
Normal file
84
l4g/logger.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package l4g
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
const (
|
||||
LOGGER_TYPE_TERMINAL = "terminal"
|
||||
LOGGER_TYPE_FILE = "file"
|
||||
LOGGER_TYPE_DATABASE = "database"
|
||||
)
|
||||
|
||||
var globalLogger Logger
|
||||
|
||||
const (
|
||||
CATEGORY_SYSTEM int32 = 0 // System-level logging, such as server starting, database connection problems, etc.
|
||||
CATEGORY_ADMIN int32 = 1 // Administrative logging, used for CRM, and any "internal" action taken by an employee
|
||||
CATEGORY_USER int32 = 2 // General category for actions performed by / for a user (E.g. user updates their account)
|
||||
CATEGORY_ORG int32 = 3 // General category for actions performed by / for an organization (E.g. User updates company profile)
|
||||
CATEGORY_AUDIT int32 = 4 // Category for transactional logging (Accept request, decline, modified transaction record)
|
||||
CATEGORY_AUTH int32 = 5 // Authentication / Authorization messages (User logged in, failed login attempts, user failed authorization check, out-of-country IP, etc)
|
||||
)
|
||||
|
||||
const (
|
||||
TYPE_DEBUG int32 = 0 // Testing and debugging log. Should not really be used too often since debug logging statements should be cleaned once an issue is resolved.
|
||||
TYPE_INFO int32 = 1 // General log message
|
||||
TYPE_WARN int32 = 2 // "Incorrect" actions taken. Includes failed login attempts, validation issues, etc. Nothing that is a "problem" for us, just malformed user input.
|
||||
TYPE_ERROR int32 = 3 // Failed program state/behavior. Used for issues such as database connection failures, web API connection failures, etc.
|
||||
|
||||
TYPE_CREATE int32 = 4 // Resource created - Does not need to be broken down to each individual row created in a db table, but rather the "logical action" taken by a user.
|
||||
TYPE_READ int32 = 5 // Resource consumed - Does not need to be broken down to each individual row created in a db table, but rather the "logical action" taken by a user.
|
||||
TYPE_UPDATE int32 = 6 // Resource updated - Does not need to be broken down to each individual row created in a db table, but rather the "logical action" taken by a user.
|
||||
TYPE_DELETE int32 = 7 // Resource deleted - Does not need to be broken down to each individual row created in a db table, but rather the "logical action" taken by a user.
|
||||
)
|
||||
|
||||
type Logger interface {
|
||||
Write(entry Entry) error
|
||||
Fatal(entry Entry) error
|
||||
}
|
||||
|
||||
func Init(loggerType string, logFilePath ...string) error {
|
||||
switch loggerType {
|
||||
case LOGGER_TYPE_TERMINAL:
|
||||
globalLogger = NewTerminalLogger()
|
||||
case LOGGER_TYPE_FILE:
|
||||
filePath := "./app.log"
|
||||
if len(logFilePath) > 0 && logFilePath[0] != "" {
|
||||
filePath = logFilePath[0]
|
||||
}
|
||||
globalLogger = NewFileLogger(filePath)
|
||||
case LOGGER_TYPE_DATABASE:
|
||||
globalLogger = NewDatabaseLogger()
|
||||
default:
|
||||
globalLogger = NewTerminalLogger()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetLogger() Logger {
|
||||
if globalLogger == nil {
|
||||
globalLogger = NewTerminalLogger()
|
||||
}
|
||||
return globalLogger
|
||||
}
|
||||
|
||||
func Serialize(data any) *string {
|
||||
var structuredJSON *string
|
||||
if data != nil {
|
||||
if jsonBytes, err := json.Marshal(data); err == nil {
|
||||
jsonString := string(jsonBytes)
|
||||
structuredJSON = &jsonString
|
||||
}
|
||||
}
|
||||
|
||||
return structuredJSON
|
||||
}
|
||||
|
||||
func Write(entry Entry) error {
|
||||
return GetLogger().Write(entry)
|
||||
}
|
||||
|
||||
func Fatal(entry Entry) error {
|
||||
return GetLogger().Fatal(entry)
|
||||
}
|
||||
71
l4g/terminal.go
Normal file
71
l4g/terminal.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package l4g
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TerminalLogger struct{}
|
||||
|
||||
func NewTerminalLogger() *TerminalLogger {
|
||||
return &TerminalLogger{}
|
||||
}
|
||||
|
||||
func (t *TerminalLogger) Write(entry Entry) error {
|
||||
timestamp := entry.Timestamp.Format(time.RFC3339)
|
||||
|
||||
var category string
|
||||
switch entry.Category {
|
||||
case CATEGORY_SYSTEM:
|
||||
category = "SYSTEM"
|
||||
case CATEGORY_ADMIN:
|
||||
category = "ADMIN"
|
||||
case CATEGORY_USER:
|
||||
category = "USER"
|
||||
case CATEGORY_ORG:
|
||||
category = "ORG"
|
||||
case CATEGORY_AUDIT:
|
||||
category = "AUDIT"
|
||||
case CATEGORY_AUTH:
|
||||
category = "AUTH"
|
||||
default:
|
||||
category = "UNKNOWN"
|
||||
}
|
||||
|
||||
var logType string
|
||||
switch entry.LogType {
|
||||
case TYPE_DEBUG:
|
||||
logType = "DEBUG"
|
||||
case TYPE_INFO:
|
||||
logType = "INFO"
|
||||
case TYPE_WARN:
|
||||
logType = "WARN"
|
||||
case TYPE_ERROR:
|
||||
logType = "ERROR"
|
||||
default:
|
||||
logType = "UNKNOWN"
|
||||
}
|
||||
|
||||
content := ""
|
||||
if entry.Content != nil {
|
||||
content = *entry.Content
|
||||
}
|
||||
|
||||
fmt.Printf("[%s] %s/%s: %s\n", timestamp, category, logType, content)
|
||||
|
||||
if entry.StructuredContent != nil {
|
||||
fmt.Printf("Details: %s\n", *entry.StructuredContent)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TerminalLogger) Fatal(entry Entry) error {
|
||||
err := t.Write(entry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
os.Exit(1)
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user