altnrslog

package module
v0.4.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 8, 2024 License: MIT Imports: 8 Imported by: 0

README

altnrslog

Go Reference GitHub go.mod Go version (subdirectory of monorepo) GitHub release (latest by date) codecov Go Report Card GitHub License

altnrslog is an alternative library for New Relic Logs in Context with log/slog.

altnrslog can also forward slog.Attr even only APM Agent.

Roadmap to release stable version

  • Transaction Scope
    • Supports Logs in Context with APM Agent
    • Supports Logs in Context without APM Agent
  • Application Scope CANCELED #32
    • Supports Logs in Context with APM Agent
    • Supports Logs in Context without APM Agent

Getting started

Installation
go get github.com/miyamo2/altnrslog
Simple Usage
package main

import (
	"encoding/json"
	"fmt"
	"github.com/miyamo2/altnrslog"
	"github.com/newrelic/go-agent/v3/newrelic"
	"log"
	"log/slog"
	"net/http"
	"os"
)

type IntroduceRequest struct {
	Name string `json:"name"`
}

func main() {
	nr, err := newrelic.NewApplication(
		newrelic.ConfigAppName(os.Getenv("NEW_RELIC_CONFIG_APP_NAME")),
		newrelic.ConfigLicense(os.Getenv("NEW_RELIC_CONFIG_LICENSE")),
		newrelic.ConfigAppLogForwardingEnabled(true),
	)
	if err != nil {
		panic(err)
	}

	http.HandleFunc(newrelic.WrapHandleFunc(nr, "/introduce", func(w http.ResponseWriter, r *http.Request) {
		ctx := r.Context()
		tx := newrelic.FromContext(ctx)
		logHandler := altnrslog.NewTransactionalHandler(nr, tx)
		logger := slog.New(logHandler)

		var req IntroduceRequest
		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		logger.InfoContext(ctx, "START", slog.Group("request", slog.String("name", req.Name)))

		response := fmt.Sprintf("Hello, %s!", req.Name)
		defer logger.InfoContext(ctx, "END", slog.String("response", response))

		w.Write([]byte(response))
	}))

	log.Fatal(http.ListenAndServe(":8080", nil))
}

Contributing

Feel free to open PR or an Issue.

Documentation

Overview

altnrslog is an alternative library for New Relic Logs in Context with log/slog.

altnrslog can also forward slog.Attr even only APM Agent.

Example
package main

import (
	"encoding/json"
	"fmt"
	"log"
	"log/slog"
	"net/http"
	"os"

	"github.com/miyamo2/altnrslog"
	"github.com/newrelic/go-agent/v3/newrelic"
)

func main() {
	type Request struct {
		Name string `json:"name"`
	}

	nr, err := newrelic.NewApplication(
		newrelic.ConfigAppName(os.Getenv("NEW_RELIC_CONFIG_APP_NAME")),
		newrelic.ConfigLicense(os.Getenv("NEW_RELIC_CONFIG_LICENSE")),
		newrelic.ConfigAppLogForwardingEnabled(true),
	)
	if err != nil {
		panic(err)
	}

	httpHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		ctx := r.Context()
		tx := newrelic.FromContext(ctx)
		logHandler := altnrslog.NewTransactionalHandler(nr, tx)
		logger := slog.New(logHandler)

		var req Request
		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		logger.InfoContext(ctx, "START", slog.Group("request", slog.String("name", req.Name)))

		response := fmt.Sprintf("Hello, %s!", req.Name)
		defer logger.InfoContext(ctx, "END", slog.String("response", response))

		w.Write([]byte(response))
	})
	http.Handle(newrelic.WrapHandle(nr, "/introduce", httpHandler))

	log.Fatal(http.ListenAndServe(":8080", nil))
}
Output:

Example (WithCustomMiddleware)
package main

import (
	"encoding/json"
	"fmt"
	"log"
	"log/slog"
	"net/http"
	"os"

	"github.com/miyamo2/altnrslog"
	"github.com/newrelic/go-agent/v3/newrelic"
)

func main() {
	type Request struct {
		Name string `json:"name"`
	}

	nr, err := newrelic.NewApplication(
		newrelic.ConfigAppName(os.Getenv("NEW_RELIC_CONFIG_APP_NAME")),
		newrelic.ConfigLicense(os.Getenv("NEW_RELIC_CONFIG_LICENSE")),
		newrelic.ConfigAppLogForwardingEnabled(true),
	)
	if err != nil {
		panic(err)
	}

	httpHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		ctx := r.Context()
		tx := newrelic.FromContext(ctx)
		logHandler := altnrslog.NewTransactionalHandler(nr, tx)
		logger := slog.New(logHandler)

		var req Request
		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		logger.InfoContext(ctx, "START", slog.Group("request", slog.String("name", req.Name)))

		response := fmt.Sprintf("Hello, %s!", req.Name)
		defer logger.InfoContext(ctx, "END", slog.String("response", response))

		w.Write([]byte(response))
	})

	middleware := func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			ctx := r.Context()
			tx := newrelic.FromContext(ctx)
			logHandler := altnrslog.NewTransactionalHandler(nr, tx)
			logger := slog.New(logHandler)
			ctx, err := altnrslog.StoreToContext(ctx, logger)
			if err != nil {
				http.Error(w, err.Error(), http.StatusInternalServerError)
				return
			}
			next.ServeHTTP(w, r.WithContext(ctx))
		})
	}

	http.Handle(newrelic.WrapHandle(nr, "/introduce", middleware(httpHandler)))

	log.Fatal(http.ListenAndServe(":8080", nil))
}
Output:

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// ErrInvalidHandler is returned when the handler is not a TransactionalHandler.
	ErrInvalidHandler = errors.New("invalid handler")
	// ErrNotStored is returned when the logger is not stored in context.Context.
	ErrNotStored = errors.New("logger not stored")
)

Functions

func FromContext

func FromContext(ctx context.Context) (*slog.Logger, error)

FromContext returns *slog.Logger with *TransactionalHandler, stored in the context.Context. If it does not exist, return ErrNotStored.

func StoreToContext

func StoreToContext(ctx context.Context, logger *slog.Logger) (context.Context, error)

StoreToContext stores the *slog.Logger in context.Context. Logger must be set to *TransactionalHandler in the Handler

Types

type HandlerOption

type HandlerOption func(*Properties)

HandlerOption is a functional option for creating a new TransactionalHandler.

func WithInnerHandlerProvider added in v0.2.0

func WithInnerHandlerProvider(innerHandlerProvider InnerHandlerProvider) HandlerOption

WithInnerHandlerProvider specifies the function that provides the slog.Handler to be wrapped.

func WithInnerWriter

func WithInnerWriter(w io.Writer) HandlerOption

WithInnerWriter specifies the io.Writer that wraps logWriter.logWriter

func WithLogLevel added in v0.4.0

func WithLogLevel(level slog.Level) HandlerOption

WithLogLevel specifies the log level. if not specified, the default is slog.LevelInfo. if lower than the inner handler's level, the inner handler's level will be used.

func WithSlogHandlerSpecify

func WithSlogHandlerSpecify(json bool, o *slog.HandlerOptions) HandlerOption

WithSlogHandlerSpecify specifies whether to use JSON format and slog.HandlerOptions.

type InnerHandlerProvider added in v0.2.0

type InnerHandlerProvider func(io.Writer) slog.Handler

type Properties

type Properties struct {
	// contains filtered or unexported fields
}

Properties is an options for creating a new TransactionalHandler.

type TransactionalHandler

type TransactionalHandler struct {
	// contains filtered or unexported fields
}

TransactionalHandler is a slog.Handler that adds New Relic distributed tracing metadata to log records.

func NewTransactionalHandler

func NewTransactionalHandler(app *newrelic.Application, tx *newrelic.Transaction, options ...HandlerOption) *TransactionalHandler

NewTransactionalHandler is constructor for TransactionalHandler.

Example
package main

import (
	"log/slog"
	"os"

	"github.com/miyamo2/altnrslog"
	"github.com/newrelic/go-agent/v3/newrelic"
)

func main() {
	app, err := newrelic.NewApplication(
		newrelic.ConfigAppName(os.Getenv("NEW_RELIC_CONFIG_APP_NAME")),
		newrelic.ConfigLicense(os.Getenv("NEW_RELIC_CONFIG_LICENSE")),
		newrelic.ConfigAppLogForwardingEnabled(true),
	)
	tx := app.StartTransaction("ExampleNewTransactionalHandler")
	if err != nil {
		panic(err)
	}

	txHandler := altnrslog.NewTransactionalHandler(app, tx)
	slog.New(txHandler)
}
Output:

Example (WithInnerHandlerProvider)
package main

import (
	"fmt"
	"io"
	"log/slog"
	"os"

	"github.com/miyamo2/altnrslog"
	"github.com/newrelic/go-agent/v3/newrelic"
)

func main() {
	app, err := newrelic.NewApplication(
		newrelic.ConfigAppName(os.Getenv("NEW_RELIC_CONFIG_APP_NAME")),
		newrelic.ConfigLicense(os.Getenv("NEW_RELIC_CONFIG_LICENSE")),
		newrelic.ConfigAppLogForwardingEnabled(true),
	)
	tx := app.StartTransaction("ExampleNewTransactionalHandler_withInnerHandlerProvider")
	if err != nil {
		panic(err)
	}

	handlerOpt := slog.HandlerOptions{
		AddSource: true,
		ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
			return slog.Attr{
				Key:   fmt.Sprintf("replaced.%s.%s", groups[0], a.Key),
				Value: a.Value,
			}
		},
	}

	jsonHandlerProvider := func(w io.Writer) slog.Handler {
		return slog.NewJSONHandler(w, &handlerOpt)
	}

	txHandler := altnrslog.NewTransactionalHandler(app, tx,
		altnrslog.WithInnerHandlerProvider(jsonHandlerProvider))

	slog.New(txHandler)
}
Output:

Example (WithInnerWriter)
package main

import (
	"io"
	"log/slog"
	"os"

	"github.com/miyamo2/altnrslog"
	"github.com/newrelic/go-agent/v3/newrelic"
)

func main() {
	app, err := newrelic.NewApplication(
		newrelic.ConfigAppName(os.Getenv("NEW_RELIC_CONFIG_APP_NAME")),
		newrelic.ConfigLicense(os.Getenv("NEW_RELIC_CONFIG_LICENSE")),
		newrelic.ConfigAppLogForwardingEnabled(true),
	)
	tx := app.StartTransaction("ExampleNewTransactionalHandler_withInnerWriter")
	if err != nil {
		panic(err)
	}

	logFile, err := os.OpenFile("log.txt", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666)
	if err != nil {
		panic(err)
	}
	mw := io.MultiWriter(os.Stdout, logFile)
	txHandler := altnrslog.NewTransactionalHandler(app, tx, altnrslog.WithInnerWriter(mw))

	slog.New(txHandler)
}
Output:

Example (WithSlogHandlerSpecify)
package main

import (
	"fmt"
	"log/slog"
	"os"

	"github.com/miyamo2/altnrslog"
	"github.com/newrelic/go-agent/v3/newrelic"
)

func main() {
	app, err := newrelic.NewApplication(
		newrelic.ConfigAppName(os.Getenv("NEW_RELIC_CONFIG_APP_NAME")),
		newrelic.ConfigLicense(os.Getenv("NEW_RELIC_CONFIG_LICENSE")),
		newrelic.ConfigAppLogForwardingEnabled(true),
	)
	tx := app.StartTransaction("ExampleNewTransactionalHandler_withSlogHandlerSpecify")
	if err != nil {
		panic(err)
	}

	txHandler := altnrslog.NewTransactionalHandler(app, tx,
		altnrslog.WithSlogHandlerSpecify(true, &slog.HandlerOptions{
			AddSource: true,
			ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
				return slog.Attr{
					Key:   fmt.Sprintf("replaced.%s.%s", groups[0], a.Key),
					Value: a.Value,
				}
			},
		}))

	slog.New(txHandler)
}
Output:

func (*TransactionalHandler) Enabled

func (h *TransactionalHandler) Enabled(ctx context.Context, level slog.Level) bool

Enabled See: slog.Handler.Enabled

func (*TransactionalHandler) Handle

Handle adds New Relic distributed tracing metadata to log records before passing them to the wrapped handler.

func (*TransactionalHandler) WithAttrs

func (h *TransactionalHandler) WithAttrs(attrs []slog.Attr) slog.Handler

WithAttrs See: slog.Handler.WithAttrs

func (*TransactionalHandler) WithGroup

func (h *TransactionalHandler) WithGroup(name string) slog.Handler

WithGroup See: slog.Handler.WithGroup

Directories

Path Synopsis
internal
mock
Package mock_slog is a generated GoMock package.
Package mock_slog is a generated GoMock package.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL