Skip to content

Commit 1902ca8

Browse files
authored
feat: rework logging (#45)
log level handling is fixed, logging allows now to log to multiple outputs/handlers
1 parent 7b3da2c commit 1902ca8

18 files changed

Lines changed: 827 additions & 149 deletions

README.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,9 @@ The OpenTelemetry exporter can be configured using the following settings in the
299299
```hcl
300300
telemetry {
301301
logging {
302-
type = "otel" # Use OpenTelemetry
302+
otel {
303+
enabled = true # Enable OpenTelemetry logging
304+
}
303305
}
304306
metrics {
305307
enabled = true # Enable metrics collection
@@ -314,15 +316,19 @@ The OTLP exporter is configured using the common OpenTelemetry environment varia
314316

315317
### slog logging
316318

317-
To use `log/slog` for logging, you can configure the telemetry logging settings in your NACP configuration file. This allows you to set the logging type, level, and handler.
319+
To use `log/slog` for logging, you can configure the telemetry logging settings in your NACP configuration file. This allows you to add json and text slog handlers.
318320

319321
```hcl
320322
telemetry {
321323
logging {
322-
type = "slog" # Use slog for logging
324+
323325
level = "info" # Set the logging level (e.g., debug, info, warn, error)
324326
slog {
325-
handler = "text" # Set the slog handler (e.g., text, json)
327+
json = true # Adds the json slog handler (defaults to false)
328+
text = true # Adds the text slog handler (defaults to false)
329+
330+
text_out = "stderr" # default "stdout"
331+
json_out = "stdout" # same
326332
}
327333
}
328334
}

cmd/nacp/nacp.go

Lines changed: 51 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424

2525
"github.com/mxab/nacp/admissionctrl/remoteutil"
2626
"github.com/mxab/nacp/admissionctrl/types"
27+
"github.com/mxab/nacp/logutil"
2728
nacpOtel "github.com/mxab/nacp/otel"
2829

2930
"log/slog"
@@ -39,7 +40,6 @@ import (
3940
"github.com/notaryproject/notation-go/dir"
4041
"github.com/notaryproject/notation-go/verifier/truststore"
4142

42-
"go.opentelemetry.io/contrib/bridges/otelslog"
4343
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
4444
)
4545

@@ -111,15 +111,15 @@ func resolveTokenAccessor(ctx context.Context, transport http.RoundTripper, noma
111111

112112
return &aclToken, nil
113113
}
114-
func NewProxyAsHandlerFunc(nomadAddress *url.URL, jobHandler *admissionctrl.JobHandler, appLogger *slog.Logger, transport http.RoundTripper) http.HandlerFunc {
114+
func NewProxyAsHandlerFunc(nomadAddress *url.URL, jobHandler *admissionctrl.JobHandler, logger *slog.Logger, transport http.RoundTripper) http.HandlerFunc {
115115

116-
proxy := newProxyHandler(nomadAddress, jobHandler, appLogger, transport)
116+
proxy := newProxyHandler(nomadAddress, jobHandler, logger, transport)
117117
handlerFunc := http.HandlerFunc(proxy)
118118
handlerFunc = otelhttp.NewHandler(handlerFunc, "/").(http.HandlerFunc)
119119

120120
return handlerFunc
121121
}
122-
func newProxyHandler(nomadAddress *url.URL, jobHandler *admissionctrl.JobHandler, appLogger *slog.Logger, transport http.RoundTripper) func(http.ResponseWriter, *http.Request) {
122+
func newProxyHandler(nomadAddress *url.URL, jobHandler *admissionctrl.JobHandler, logger *slog.Logger, transport http.RoundTripper) func(http.ResponseWriter, *http.Request) {
123123

124124
proxy := httputil.NewSingleHostReverseProxy(nomadAddress)
125125

@@ -138,14 +138,14 @@ func newProxyHandler(nomadAddress *url.URL, jobHandler *admissionctrl.JobHandler
138138
var err error
139139

140140
if isRegister(resp.Request) {
141-
err = handRegisterResponse(resp, appLogger)
141+
err = handRegisterResponse(resp, logger)
142142
} else if isPlan(resp.Request) {
143-
err = handleJobPlanResponse(resp, appLogger)
143+
err = handleJobPlanResponse(resp, logger)
144144
} else if isValidate(resp.Request) {
145-
err = handleJobValdidateResponse(resp, appLogger)
145+
err = handleJobValdidateResponse(resp, logger)
146146
}
147147
if err != nil {
148-
appLogger.ErrorContext(resp.Request.Context(), "Preparing response failed", "error", err)
148+
logger.ErrorContext(resp.Request.Context(), "Preparing response failed", "error", err)
149149
return err
150150
}
151151

@@ -166,34 +166,34 @@ func newProxyHandler(nomadAddress *url.URL, jobHandler *admissionctrl.JobHandler
166166
if jobHandler.ResolveToken() {
167167
tokenInfo, err := resolveTokenAccessor(ctx, transport, nomadAddress, token)
168168
if err != nil {
169-
appLogger.ErrorContext(ctx, "Resolving token failed", "error", err)
169+
logger.ErrorContext(ctx, "Resolving token failed", "error", err)
170170
writeError(w, err)
171171
}
172172
if tokenInfo != nil {
173173
reqCtx.AccessorID = tokenInfo.AccessorID
174174
reqCtx.TokenInfo = tokenInfo
175175
}
176-
appLogger.InfoContext(ctx, "Request received", "path", r.URL.Path, "method", r.Method, "clientIP", reqCtx.ClientIP, "accessorID", reqCtx.AccessorID)
176+
logger.InfoContext(ctx, "Request received", "path", r.URL.Path, "method", r.Method, "clientIP", reqCtx.ClientIP, "accessorID", reqCtx.AccessorID)
177177
} else {
178-
appLogger.InfoContext(ctx, "Request received", "path", r.URL.Path, "method", r.Method, "clientIP", reqCtx.ClientIP)
178+
logger.InfoContext(ctx, "Request received", "path", r.URL.Path, "method", r.Method, "clientIP", reqCtx.ClientIP)
179179
}
180180

181181
ctx = context.WithValue(ctx, "request_context", reqCtx)
182182
r = r.WithContext(ctx)
183183

184184
var err error
185185
if isRegister(r) {
186-
r, err = handleRegister(r, appLogger, jobHandler)
186+
r, err = handleRegister(r, logger, jobHandler)
187187

188188
} else if isPlan(r) {
189-
r, err = handlePlan(r, appLogger, jobHandler)
189+
r, err = handlePlan(r, logger, jobHandler)
190190

191191
} else if isValidate(r) {
192-
r, err = handleValidate(r, appLogger, jobHandler)
192+
r, err = handleValidate(r, logger, jobHandler)
193193

194194
}
195195
if err != nil {
196-
appLogger.WarnContext(ctx, "Error applying admission controllers", "error", err)
196+
logger.WarnContext(ctx, "Error applying admission controllers", "error", err)
197197
writeError(w, err)
198198

199199
} else {
@@ -205,7 +205,7 @@ func newProxyHandler(nomadAddress *url.URL, jobHandler *admissionctrl.JobHandler
205205

206206
}
207207

208-
func handRegisterResponse(resp *http.Response, applogger *slog.Logger) error {
208+
func handRegisterResponse(resp *http.Response, logger *slog.Logger) error {
209209

210210
warnings, ok := resp.Request.Context().Value(ctxWarnings).([]error)
211211
if !ok && len(warnings) == 0 {
@@ -254,7 +254,7 @@ func checkIfGzipAndTransformReader(resp *http.Response, reader io.ReadCloser) (b
254254
}
255255
return isGzip, reader, nil
256256
}
257-
func handleJobPlanResponse(resp *http.Response, applogger *slog.Logger) error {
257+
func handleJobPlanResponse(resp *http.Response, logger *slog.Logger) error {
258258
warnings, ok := resp.Request.Context().Value(ctxWarnings).([]error)
259259
if !ok && len(warnings) == 0 {
260260
return nil
@@ -286,7 +286,7 @@ func handleJobPlanResponse(resp *http.Response, applogger *slog.Logger) error {
286286
}
287287
return nil
288288
}
289-
func handleJobValdidateResponse(resp *http.Response, appLogger *slog.Logger) error {
289+
func handleJobValdidateResponse(resp *http.Response, logger *slog.Logger) error {
290290

291291
ctx := resp.Request.Context()
292292
validationErr, okErr := ctx.Value(ctxValidationError).(error)
@@ -528,20 +528,26 @@ func isValidate(r *http.Request) bool {
528528
return (r.Method == "PUT" || r.Method == "POST") && r.URL.Path == "/v1/validate/job"
529529
}
530530

531+
func buildSlogHandler(json bool, level slog.Level) slog.Handler {
532+
opts := &slog.HandlerOptions{
533+
Level: level,
534+
}
535+
if json {
536+
return slog.NewJSONHandler(os.Stdout, opts)
537+
}
538+
return slog.NewTextHandler(os.Stdout, opts)
539+
}
540+
531541
// https://www.codedodle.com/go-reverse-proxy-example.html
532542
// https://joshsoftware.wordpress.com/2021/05/25/simple-and-powerful-reverseproxy-in-go/
543+
533544
func main() {
534545

535546
configPtr := flag.String("config", "", "point to a nacp config file")
536547
bootstrapLoggerHandlerPtr := flag.Bool("bootstrap-json-logger", false, "use json for initial logging until config is loaded")
537548
flag.Parse()
538-
var bootstrapLogger *slog.Logger
539-
if *bootstrapLoggerHandlerPtr {
540-
bootstrapLogger = slog.New(slog.NewJSONHandler(os.Stdout, nil))
541-
} else {
542-
bootstrapLogger = slog.New(slog.NewTextHandler(os.Stdout, nil))
543-
}
544-
slog.SetDefault(bootstrapLogger)
549+
550+
slog.SetDefault(slog.New(buildSlogHandler(*bootstrapLoggerHandlerPtr, slog.LevelInfo)))
545551

546552
c, err := buildConfig(*configPtr)
547553
if err != nil {
@@ -561,37 +567,27 @@ func run(c *config.Config) (err error) {
561567
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
562568
defer stop()
563569

564-
loggerFactory, err := buildLoggerFactory(c)
565-
if err != nil {
566-
return fmt.Errorf("failed to build logger factory: %w", err)
567-
}
570+
rootFactory, leveler := logutil.NewLoggerFactoryFromConfig(c.Telemetry.Logging)
571+
572+
appLogger := rootFactory.GetLogger("nacp")
573+
slog.SetDefault(appLogger)
568574

569-
logger := loggerFactory("nacp")
570-
slog.SetDefault(logger)
571-
appLogger := logger
572-
setupOtel := c.Telemetry.Logging.IsOtel() || c.Telemetry.Metrics.Enabled || c.Telemetry.Tracing.Enabled
575+
setupOtel := *c.Telemetry.Logging.OtelLogging.Enabled || c.Telemetry.Metrics.Enabled || c.Telemetry.Tracing.Enabled
573576
if setupOtel {
574577
// Set up OpenTelemetry.
575-
otelShutdown, err := nacpOtel.SetupOTelSDK(ctx, c.Telemetry.Logging.IsOtel(), c.Telemetry.Metrics.Enabled, c.Telemetry.Tracing.Enabled, version)
578+
otelShutdown, err := nacpOtel.SetupOTelSDK(ctx, *c.Telemetry.Logging.OtelLogging.Enabled, c.Telemetry.Metrics.Enabled, c.Telemetry.Tracing.Enabled, version, leveler.GetSeverietier())
576579
if err != nil {
577580
return fmt.Errorf("failed to setup OpenTelemetry: %w", err)
578581
}
579582
// Handle shutdown properly so nothing leaks.
580583
// https://opentelemetry.io/docs/languages/go/getting-started/
581584
defer func() {
582585
err = errors.Join(err, otelShutdown(context.Background()))
583-
584586
}()
585587

586588
}
587589

588-
level := slog.Level(0)
589-
if err := level.UnmarshalText([]byte(c.Telemetry.Logging.Level)); err != nil {
590-
return fmt.Errorf("failed to parse log level: %w", err)
591-
}
592-
slog.SetLogLoggerLevel(level)
593-
594-
server, err := buildServer(c, loggerFactory)
590+
server, err := buildServer(c, rootFactory)
595591

596592
if err != nil {
597593
return fmt.Errorf("failed to build server: %w", err)
@@ -627,34 +623,7 @@ func run(c *config.Config) (err error) {
627623

628624
}
629625

630-
func buildLoggerFactory(c *config.Config) (lf loggerFactory, err error) {
631-
if c.Telemetry.Logging.IsSlog() {
632-
if c.Telemetry.Logging.SlogLogging.Handler == "json" {
633-
lf = func(_ string) *slog.Logger {
634-
return slog.New(slog.NewJSONHandler(os.Stdout, nil))
635-
}
636-
637-
} else if c.Telemetry.Logging.SlogLogging.Handler == "text" {
638-
lf = func(_ string) *slog.Logger {
639-
return slog.New(slog.NewTextHandler(os.Stdout, nil))
640-
}
641-
642-
} else {
643-
return nil, fmt.Errorf("invalid slog logging handler, only json and text are supported")
644-
}
645-
646-
} else if c.Telemetry.Logging.IsOtel() {
647-
lf = func(name string) *slog.Logger {
648-
return otelslog.NewLogger(name)
649-
}
650-
651-
} else {
652-
return nil, fmt.Errorf("invalid logging type, only slog and otel are supported")
653-
}
654-
return
655-
}
656-
657-
func buildServer(c *config.Config, loggerFactory loggerFactory) (*http.Server, error) {
626+
func buildServer(c *config.Config, loggerFactory *logutil.LoggerFactory) (*http.Server, error) {
658627
backend, err := url.Parse(c.Nomad.Address)
659628
if err != nil {
660629
return nil, fmt.Errorf("failed to parse nomad address: %w", err)
@@ -696,11 +665,11 @@ func buildServer(c *config.Config, loggerFactory loggerFactory) (*http.Server, e
696665

697666
jobMutators,
698667
jobValidators,
699-
loggerFactory("handler"),
668+
loggerFactory.GetLogger("handler"),
700669
resolveToken,
701670
)
702671

703-
handlerFunc := NewProxyAsHandlerFunc(backend, jobHandler, loggerFactory("proxy-handler"), instrumentedProxyTransport)
672+
handlerFunc := NewProxyAsHandlerFunc(backend, jobHandler, loggerFactory.GetLogger("proxy-handler"), instrumentedProxyTransport)
704673

705674
bind := fmt.Sprintf("%s:%d", c.Bind, c.Port)
706675
var tlsConfig *tls.Config
@@ -760,7 +729,7 @@ func createTlsConfig(caFile string, noClientCert bool) (*tls.Config, error) {
760729
return tlsConfig, nil
761730
}
762731

763-
func createMutators(c *config.Config, loggerFactory loggerFactory) ([]admissionctrl.JobMutator, bool, error) {
732+
func createMutators(c *config.Config, loggerFactory *logutil.LoggerFactory) ([]admissionctrl.JobMutator, bool, error) {
764733
var jobMutators []admissionctrl.JobMutator
765734
var resolveToken bool
766735
for _, m := range c.Mutators {
@@ -769,18 +738,18 @@ func createMutators(c *config.Config, loggerFactory loggerFactory) ([]admissionc
769738
}
770739
switch m.Type {
771740
case "opa_json_patch":
772-
notationVerifier, err := buildVerifierIfEnabled(m.OpaRule.Notation, loggerFactory("notation_verifier"))
741+
notationVerifier, err := buildVerifierIfEnabled(m.OpaRule.Notation, loggerFactory.GetLogger("notation_verifier"))
773742
if err != nil {
774743
return nil, resolveToken, err
775744
}
776-
mutator, err := mutator.NewOpaJsonPatchMutator(m.Name, m.OpaRule.Filename, m.OpaRule.Query, loggerFactory("opa_mutator"), notationVerifier)
745+
mutator, err := mutator.NewOpaJsonPatchMutator(m.Name, m.OpaRule.Filename, m.OpaRule.Query, loggerFactory.GetLogger("opa_mutator"), notationVerifier)
777746
if err != nil {
778747
return nil, resolveToken, err
779748
}
780749
jobMutators = append(jobMutators, mutator)
781750

782751
case "json_patch_webhook":
783-
mutator, err := mutator.NewJsonPatchWebhookMutator(m.Name, m.Webhook.Endpoint, m.Webhook.Method, loggerFactory("json_patch_webhook_mutator"))
752+
mutator, err := mutator.NewJsonPatchWebhookMutator(m.Name, m.Webhook.Endpoint, m.Webhook.Method, loggerFactory.GetLogger("json_patch_webhook_mutator"))
784753
if err != nil {
785754
return nil, resolveToken, err
786755
}
@@ -793,7 +762,7 @@ func createMutators(c *config.Config, loggerFactory loggerFactory) ([]admissionc
793762
}
794763
return jobMutators, resolveToken, nil
795764
}
796-
func createValidators(c *config.Config, loggerFactory loggerFactory) ([]admissionctrl.JobValidator, bool, error) {
765+
func createValidators(c *config.Config, loggerFactory *logutil.LoggerFactory) ([]admissionctrl.JobValidator, bool, error) {
797766
var jobValidators []admissionctrl.JobValidator
798767
var resolveToken bool
799768
for _, v := range c.Validators {
@@ -802,28 +771,28 @@ func createValidators(c *config.Config, loggerFactory loggerFactory) ([]admissio
802771
}
803772
switch v.Type {
804773
case "opa":
805-
notationVerifier, err := buildVerifierIfEnabled(v.Notation, loggerFactory("notation_verifier"))
774+
notationVerifier, err := buildVerifierIfEnabled(v.Notation, loggerFactory.GetLogger("notation_verifier"))
806775
if err != nil {
807776
return nil, resolveToken, err
808777
}
809-
opaValidator, err := validator.NewOpaValidator(v.Name, v.OpaRule.Filename, v.OpaRule.Query, loggerFactory("opa_validator"), notationVerifier)
778+
opaValidator, err := validator.NewOpaValidator(v.Name, v.OpaRule.Filename, v.OpaRule.Query, loggerFactory.GetLogger("opa_validator"), notationVerifier)
810779
if err != nil {
811780
return nil, resolveToken, err
812781
}
813782
jobValidators = append(jobValidators, opaValidator)
814783

815784
case "webhook":
816-
validator, err := validator.NewWebhookValidator(v.Name, v.Webhook.Endpoint, v.Webhook.Method, loggerFactory("webhook_validator"))
785+
validator, err := validator.NewWebhookValidator(v.Name, v.Webhook.Endpoint, v.Webhook.Method, loggerFactory.GetLogger("webhook_validator"))
817786
if err != nil {
818787
return nil, resolveToken, err
819788
}
820789
jobValidators = append(jobValidators, validator)
821790
case "notation":
822-
notationVerifier, err := buildVerifier(v.Notation, loggerFactory("notation_verifier"))
791+
notationVerifier, err := buildVerifier(v.Notation, loggerFactory.GetLogger("notation_verifier"))
823792
if err != nil {
824793
return nil, resolveToken, err
825794
}
826-
validator := validator.NewNotationValidator(loggerFactory("notation_validator"), v.Name, notationVerifier)
795+
validator := validator.NewNotationValidator(loggerFactory.GetLogger("notation_validator"), v.Name, notationVerifier)
827796

828797
jobValidators = append(jobValidators, validator)
829798

0 commit comments

Comments
 (0)