Files
hanko/backend/audit_log/logger.go
bjoern-m ca62cf421f feat: introduce hanko profile element and related api changes (#495)
* feat: introduce hanko profile element and related api changes
2023-01-25 10:55:23 +01:00

116 lines
3.2 KiB
Go

package auditlog
import (
"fmt"
"github.com/gofrs/uuid"
"github.com/labstack/echo/v4"
zeroLog "github.com/rs/zerolog"
zeroLogger "github.com/rs/zerolog/log"
"github.com/teamhanko/hanko/backend/config"
"github.com/teamhanko/hanko/backend/persistence"
"github.com/teamhanko/hanko/backend/persistence/models"
"os"
"strconv"
"time"
)
type Logger interface {
Create(echo.Context, models.AuditLogType, *models.User, error) error
}
type logger struct {
persister persistence.Persister
storageEnabled bool
logger zeroLog.Logger
consoleLoggingEnabled bool
}
func NewLogger(persister persistence.Persister, cfg config.AuditLog) Logger {
var loggerOutput *os.File = nil
switch cfg.ConsoleOutput.OutputStream {
case config.OutputStreamStdOut:
loggerOutput = os.Stdout
case config.OutputStreamStdErr:
loggerOutput = os.Stderr
default:
loggerOutput = os.Stdout
}
return &logger{
persister: persister,
storageEnabled: cfg.Storage.Enabled,
logger: zeroLog.New(loggerOutput),
consoleLoggingEnabled: cfg.ConsoleOutput.Enabled,
}
}
func (c *logger) Create(context echo.Context, auditLogType models.AuditLogType, user *models.User, logError error) error {
if c.storageEnabled {
err := c.store(context, auditLogType, user, logError)
if err != nil {
return err
}
}
if c.consoleLoggingEnabled {
c.logToConsole(context, auditLogType, user, logError)
}
return nil
}
func (c *logger) store(context echo.Context, auditLogType models.AuditLogType, user *models.User, logError error) error {
id, err := uuid.NewV4()
if err != nil {
return fmt.Errorf("failed to create id: %w", err)
}
var userId *uuid.UUID = nil
var userEmail *string = nil
if user != nil {
userId = &user.ID
if e := user.Emails.GetPrimary(); e != nil {
userEmail = &e.Address
}
}
var errString *string = nil
if logError != nil {
// check if error is not nil, because else the string (formatted with fmt.Sprintf) would not be empty but look like this: `%!s(<nil>)`
tmp := fmt.Sprintf("%s", logError)
errString = &tmp
}
e := models.AuditLog{
ID: id,
Type: auditLogType,
Error: errString,
MetaHttpRequestId: context.Response().Header().Get(echo.HeaderXRequestID),
MetaUserAgent: context.Request().UserAgent(),
MetaSourceIp: context.RealIP(),
ActorUserId: userId,
ActorEmail: userEmail,
}
return c.persister.GetAuditLogPersister().Create(e)
}
func (c *logger) logToConsole(context echo.Context, auditLogType models.AuditLogType, user *models.User, logError error) {
now := time.Now()
loggerEvent := zeroLogger.Log().
Str("audience", "audit").
Str("type", string(auditLogType)).
AnErr("error", logError).
Str("http_request_id", context.Response().Header().Get(echo.HeaderXRequestID)).
Str("source_ip", context.RealIP()).
Str("user_agent", context.Request().UserAgent()).
Str("time", now.Format(time.RFC3339Nano)).
Str("time_unix", strconv.FormatInt(now.Unix(), 10))
if user != nil {
loggerEvent.Str("user_id", user.ID.String())
if e := user.Emails.GetPrimary(); e != nil {
loggerEvent.Str("user_email", e.Address)
}
}
loggerEvent.Send()
}