Initial add backend stuff

This commit is contained in:
2026-07-08 15:45:16 -04:00
commit a7964f9410
89 changed files with 25924 additions and 0 deletions

41
l4g/database.go Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
}