loginjector

package module
v1.0.6 Latest Latest
Warning

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

Go to latest
Published: Jul 18, 2026 License: MIT Imports: 17 Imported by: 0

README

LogInjector

LogInjector is a small logging library for Go services that need plain-text logs routed to several destinations at once — size-rotated files, the console, a Telegram chat — filtered by severity levels you control. Application code keeps logging through the standard library (log.New over an io.Writer); LogInjector supplies that writer and takes care of routing, rotation, and alerts. A Logger is safe for concurrent use.

Install

go get github.com/prorochestvo/loginjector

Requires Go 1.22+.

Quick start

package main

import (
	"github.com/prorochestvo/loginjector"
	"github.com/prorochestvo/loginjector/levels"
)

func main() {
	// size-rotated files under ./logs (5 MiB × 7 files) + timestamped console echo.
	l, err := loginjector.NewFileLogger("./logs", "app", levels.Info)
	if err != nil {
		panic(err)
	}

	l.Printf(levels.Info, "user %q signed in", "alice")
	l.Printf(levels.Debug, "dropped: below the minimum level")
}

Create one Logger in main, then hand every component its own standard-library front logger via Logger.StdLog(level, prefix) — a fixed severity and a component prefix over the shared sink. Components depend on *log.Logger (or plain io.Writer) and never import loginjector.

l, err := loginjector.NewFileLogger("./logs", "api", levels.Info,
	loginjector.WithPrinterMinLevel(levels.Warning), // file: Info and up, console: Warning and up
	loginjector.WithStdLogRedirect(levels.Warning),  // capture the global `log` package output
)
if err != nil {
	panic(err)
}

// everything at Error and above is additionally delivered to a Telegram chat.
l.Hook(
	loginjector.TelegramHandler(botToken, chatID, "errors.log", "myapp prod"),
	levels.Error, levels.Severe, levels.Critical,
)

// per-component front loggers: same sink, different prefix and severity.
// StdLog wires log.New(l.WriterAs(level), prefix, log.Lmsgprefix) for you.
dbLog := l.StdLog(levels.Info, "sqlite ")
aiLog := l.StdLog(levels.Info, "openai ")
aiErr := l.StdLog(levels.Error, "openai ")

svc := NewCompletionService(aiLog, aiErr) // takes *log.Logger, not loginjector types

Inside a component the call sites stay standard:

s.log.Printf("request sent (model=%s)", s.model) // → "openai request sent (model=gpt-4o)"
s.err.Printf("request failed: %v", err)          // severity Error → file, console, Telegram

Two front loggers per component (regular + error) is the recommended split: it keeps per-message severity without coupling the component to any logging library.

StdLog sets log.Lmsgprefix (prefix only, no emitter timestamp) precisely because the sink stamps every line itself — so each line carries exactly one timestamp. Build a front logger by hand with log.LstdFlags and you get doubled timestamps.

Levels

LogLevel is a plain int: higher means more severe, and messages below the logger's minimum are dropped. The levels sub-package ships a ready ladder — Debug, Info, Warning, Error, Severe, Critical (1..6) — plus levels.Parse("warning") for reading a level from config. The ladder is optional: declare your own constants if it does not fit. WithMinLevel(level, handler) gives one handler its own threshold; SetMinLevel changes the logger's minimum at runtime.

Handlers

A handler is any io.Writer. Built-ins:

  • RotatingFileHandler(folder, prefix, opts...) — size-rotated log files, oldest pruned. Options: WithMaxFileSize (default 5 MiB), WithMaxFiles (default 7), WithFreshStart (empty first file each run).
  • FileByFormatHandler(folder, maxFiles, nameFunc) — one file per nameFunc() value (e.g. per day).
  • TimestampedPrintHandler(opts...) — stdout echo with timestamps and multi-line indenting; the default when NewLogger gets no handlers.
  • TimestampedHandler(inner, opts...) — wraps any io.Writer to prepend timestamps (the reusable core of TimestampedPrintHandler).
  • TelegramHandler(botToken, chatID, fileName, labels...) — delivers each message as a document to a chat.
  • PrintHandler() — bare stdout output, no timestamps.

Compose them yourself when NewFileLogger is not enough:

l := loginjector.NewLogger(
	levels.Info,
	loginjector.RotatingFileHandler("./logs", "app"), // 5 MiB × 7 files by default
	loginjector.WithMinLevel(levels.Warning, loginjector.TimestampedPrintHandler()),
)

Writing logs

Method Behaviour
Printf(level, format, args...) / Print(level, args...) format and write a line
Panicf(level, ...) / Panic(level, ...) write a line, then panic (not os.Exit)
WriteLog(level, msg) (int, error) low-level write; returns joined sink errors
Write(msg) (int, error) io.Writer writing at the minimum level
WriterAs(level) io.Writer an io.Writer bound to a fixed level

Hooks

A hook taps one or more exact levels independently of the minimum level — useful for teeing alerts somewhere without lowering the global threshold. The Telegram hook in the example above keeps firing even for levels the handlers would drop. Hook returns an id; Unhook(id) removes the hook.

HTTP middleware: httptap

HTTP traffic tapping lives in the httptap sub-package (import "github.com/prorochestvo/loginjector/httptap"):

  • httptap.NewPayloadHandler(l, level, next) — debug dump of the full request and response through the logger. Authorization, Proxy-Authorization, Cookie, and Set-Cookie are always redacted. NewPayloadHandlerWithOptions adds body-size caps, extra redacted headers, and summary control (see godoc).
  • httptap.NewAccessHandler(out, next) — production access log: one line per request (timestamp, status, method, path, duration) into any io.Writer.
  • httptap.NewRecoverHandler(l, level, next, opts...) — converts a panic into a logged stack trace plus a clean HTTP 500. It buffers the response body, so do not wrap SSE, WebSocket, or other streaming routes with it.

All three take and return http.HandlerFunc and chain freely, e.g. recover(payload(access(next))). http.Flusher, http.Hijacker, and http.Pusher are preserved when the underlying ResponseWriter supports them.

Helpers

  • Errors — constructors that attach context to errors: NewTraceError (call site), NewHttpError(code) (HTTP status), NewPublicError(public, cause) (client-safe message separated from the internal cause), NewStackTraceError (full stack plus runtime info). Wrap* and *f variants integrate with errors.Is/As/Unwrap.
  • ClosersCloseOrLog, CloseOrPanic, CloseOrJoin(&err, c) and friends for defer-ing io.Closer cleanup.

Concurrency

Every sink is wrapped so the logger never calls its Write concurrently — logging from multiple goroutines is safe even when a handler itself is not thread-safe.

License

LogInjector is released under the MIT License. See LICENSE.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func CloseOrJoin added in v1.0.6

func CloseOrJoin(errp *error, c io.Closer)

CloseOrJoin closes c and joins any close error into *errp using errors.Join, which drops nil arguments and returns nil when all arguments are nil. The intended usage is

defer CloseOrJoin(&err, c)

inside a function with a named (... err error) return — the deferred call runs after the function body and joins the close error into whatever err holds at that point. A nil errp means the close error has nowhere to be stored; c is still closed and the error is silently discarded. A nil c is a no-op.

func CloseOrLog

func CloseOrLog(closer io.Closer)

CloseOrLog closes the closer and logs the error if there is one.

func CloseOrLogError added in v1.0.6

func CloseOrLogError(w io.Writer, c io.Closer)

CloseOrLogError closes c and, if Close returns a non-nil error and w is non-nil, writes the error message followed by a newline to w. It differs from CloseOrLog in that it writes to the caller-supplied writer rather than the standard library log package, making it suitable for structured logging (e.g. logger.WriterAs(level)). A nil w means the write is skipped but c is still closed. A nil c is a no-op. The write error is intentionally discarded — writing to a logger is the documented fmt.Fprint*-to-logger exception to the no-skipped-errors rule.

func CloseOrPanic

func CloseOrPanic(closer io.Closer)

CloseOrPanic closes the closer and panics if there is an error.

func FileByFormatHandler

func FileByFormatHandler(folder string, maxFilesInFolder int, fileNameGenerator func() string) io.Writer

FileByFormatHandler save messages to files by format. The file name is generated by fileNameGenerator. The folder is the directory where the files are saved. The maxFilesInFolder is the maximum number of files in the folder. No fresh-start variant exists for this handler; see RotatingFileHandler's WithFreshStart for an analogous feature on the index-based handler (there the construction-time target is known, whereas here the name is generated per-write by fileNameGenerator).

func PrintHandler

func PrintHandler() io.Writer

PrintHandler writes each message to os.Stdout as a plain line, trimming surrounding whitespace and appending a single newline. It adds no timestamp; for the timestamped console sink (which is the default when NewLogger is given no handlers) use TimestampedPrintHandler. The returned writer is mutex-guarded; the logger never calls its Write concurrently.

func RotatingFileHandler added in v1.0.6

func RotatingFileHandler(folder, prefix string, opts ...RotatingFileOption) io.Writer

RotatingFileHandler saves messages to files by number. The file name is generated from prefix and an incrementing index (e.g. prefix.00000001.log). When a file exceeds the maximum size the handler moves to the next index and prunes the oldest files so that no more than the configured maximum remain in folder. The handler appends to files and ring-prunes the oldest ones; it never overwrites a file's existing contents unless WithFreshStart is passed.

On construction the handler resumes at the highest existing index for prefix in folder (index 1 when none exist or WithFreshStart is set) and seeds the in-memory size counter from that file's on-disk size, so the first write after a restart accounts for existing content instead of writing into the oldest file and letting pruning delete the newest data. Filenames in folder that do not match the exact prefix.<8 hex>.log shape are ignored. Any stat/glob error encountered while resolving the resume point is stashed and joined into the first Write's return value.

Defaults: max file size 5 MiB, max files 7. Override with WithMaxFileSize and WithMaxFiles; use WithFreshStart to begin each run from a truncated index-1 file. FileByFormatHandler is the sibling for filename-generator-driven rotation and has no fresh-start variant (its target name is not known until the first write).

The returned writer is mutex-guarded; the logger never calls its Write concurrently.

func SetRuntimeDetailsProvider added in v1.0.6

func SetRuntimeDetailsProvider(fn func() string)

SetRuntimeDetailsProvider installs a process-wide provider that supplies additional runtime detail text appended to every StackTraceError's Runtime() output. The provider is called at most once per process; its return value is cached for the lifetime of the process, mirroring the caching behaviour of the built-in os/arch/Go-version runtime string.

Call this at startup, before any StackTraceError is constructed. Calling it after a StackTraceError has been constructed has NO effect, because the runtime string is cached on first use (see Runtime() docs). Calling it more than once: the last call before the cache is populated wins; calls after the cache is populated are no-ops.

The provider must be safe to call from any goroutine. Loginjector calls it under the same sync.Once that guards the built-in runtime string, so the provider sees no concurrent invocation; once cached, Runtime() never calls it again.

Provider must not panic; a panic is recovered and the built-in base string (os/arch/go version) is used without the extra detail.

Only one provider is active at a time; a subsequent SetRuntimeDetailsProvider call before the cache is populated replaces the first with no composition.

The provider's return value is appended verbatim to error strings; avoid control characters in the return value.

Pass nil to clear a previously-set provider (useful in tests).

func TelegramHandler

func TelegramHandler(botToken, chatID, fileName string, labels ...string) io.Writer

func TimestampedHandler added in v1.0.6

func TimestampedHandler(inner io.Writer, opts ...PrintOption) io.Writer

TimestampedHandler wraps inner so each message is prefixed with a leading timestamp before being forwarded to inner, indenting the continuation lines of a multi-line message so the body reads as a block. It is the composable form of TimestampedPrintHandler: any io.Writer (a file handler, a buffer, another sink) can be given sink-side timestamps. The returned writer is mutex-guarded; the logger never calls its Write concurrently.

The timestamp layout defaults to "2006/01/02 15:04:05". The first line of a message is prefixed with "<timestamp> "; subsequent lines are indented by the same width so multi-line payloads read as a visual block. Trailing whitespace and newlines are trimmed from the input before formatting; the output always ends with exactly one newline. An empty or whitespace-only input produces a single "<timestamp>\n" line.

WithTimeLayout and withClock apply; WithOutput is ignored because the sink is the explicit inner argument. Use TimestampedPrintHandler when you want the os.Stdout instantiation with WithOutput support.

func TimestampedPrintHandler added in v1.0.6

func TimestampedPrintHandler(opts ...PrintOption) io.Writer

TimestampedPrintHandler prints each message to stdout with a leading timestamp, indenting the continuation lines of a multi-line message so the body reads as a block. Unlike PrintHandler, which writes to stdout via a plain writer, this handler adds a timestamp; the sink is os.Stdout by default and overridable with WithOutput. The returned writer is mutex-guarded; the logger never calls its Write concurrently. It is the os.Stdout instantiation of TimestampedHandler.

The timestamp layout defaults to "2006/01/02 15:04:05". The first line of a message is prefixed with "<timestamp> "; subsequent lines are indented by the same width so multi-line payloads read as a visual block. Trailing whitespace and newlines are trimmed from the input before formatting; the output always ends with exactly one newline. An empty or whitespace-only input produces a single "<timestamp>\n" line.

func WithMinLevel added in v1.0.6

func WithMinLevel(level LogLevel, handler io.Writer) io.Writer

WithMinLevel wraps handler so that Logger treats it as a LeveledHandler with the given minimum level. The returned writer forwards Write calls to handler unchanged; the only added behaviour is the MinLevel() method that Logger.WriteLog reads via interface assertion. Use this to give one handler a threshold distinct from the parent Logger's minimumLogLevel — for example, to send only high-severity messages to the console while sending everything to a file:

file    := loginjector.RotatingFileHandler("./logs", "app")
console := loginjector.WithMinLevel(levels.Warning, loginjector.TimestampedPrintHandler())
logger  := loginjector.NewLogger(levels.Info, file, console)

The returned writer is itself thread-safe via Logger's existing ensureThreadSafe wrapping at registration. Calling WithMinLevel on an already-leveled writer replaces the inner MinLevel().

Types

type FileLoggerOption added in v1.0.6

type FileLoggerOption func(*fileLoggerConfig)

FileLoggerOption configures NewFileLogger.

func WithMaxFileCapacity added in v1.0.6

func WithMaxFileCapacity(n uint32) FileLoggerOption

WithMaxFileCapacity overrides the maximum size of a single log file in bytes before the handler rotates to the next file. The default is 5 MiB (5 * 1024 * 1024). The underlying RotatingFileHandler takes a uint32, so values larger than math.MaxUint32 are not valid; pass a reasonable capacity.

func WithMaxFilesInFolder added in v1.0.6

func WithMaxFilesInFolder(n int) FileLoggerOption

WithMaxFilesInFolder overrides the maximum number of rotated log files retained in the log folder. Older files beyond the limit are pruned on rotation. The default is 7.

func WithPrinterMinLevel added in v1.0.6

func WithPrinterMinLevel(level LogLevel) FileLoggerOption

WithPrinterMinLevel overrides the threshold at which the console printer emits messages, decoupling it from the file-handler threshold (minLevel). When not set, the printer fires at minLevel (current behaviour). When set, the console printer fires at level >= this value while the file handler continues to fire at level >= minLevel.

Has no effect when combined with WithoutPrinter — WithoutPrinter wins.

Implemented by wrapping the printer handler in WithMinLevel before adding it to the Logger handler list; the per-handler gate in Logger.WriteLog does the rest.

func WithPrinterOptions added in v1.0.6

func WithPrinterOptions(opts ...PrintOption) FileLoggerOption

WithPrinterOptions forwards PrintOption values to the TimestampedPrintHandler created by NewFileLogger. Has no effect when combined with WithoutPrinter.

func WithStdLogRedirect added in v1.0.6

func WithStdLogRedirect(level LogLevel) FileLoggerOption

WithStdLogRedirect redirects the standard library log package output to the new logger at the given level and sets log.Flags to 0. This option is OFF by default because log.SetOutput mutates global process state shared by every package in the binary — opting in is a conscious, explicit decision. If WithStdLogRedirect is passed more than once, the last call wins. If two NewFileLogger calls in the same process both use this option, the second call silently wins: log.SetOutput is global and the first logger will no longer receive standard library log output.

func WithTempDirFallback added in v1.0.6

func WithTempDirFallback() FileLoggerOption

WithTempDirFallback enables the temp-directory fallback: when the folder argument to NewFileLogger is empty, logs are written under filepath.Join(os.TempDir(), "logs") instead of returning an error. This option is OFF by default — an empty folder without this option is a hard error, so logs never silently land in an unexpected location. Note that the shared temp path is a residual risk: on multi-user systems another process could create the directory before NewFileLogger and own it with permissive permissions; NewFileLogger calls os.Chmod after MkdirAll to enforce 0750, but there is a brief window between directory creation and the chmod call.

func WithoutPrinter added in v1.0.6

func WithoutPrinter() FileLoggerOption

WithoutPrinter disables the timestamped stdout printer that NewFileLogger attaches by default. Use this when you want only the rotated file output and no console echo.

type HookID

type HookID string

HookID is a unique identifier for a hook

type HttpError

type HttpError interface {
	StatusCode() int
	error
}

HttpError is an error that provides an HTTP status code.

func NewHttpError

func NewHttpError(code int) HttpError

NewHttpError creates a new HttpError with the given status code.

func WrapHttpError added in v1.0.6

func WrapHttpError(code int, cause error) HttpError

WrapHttpError creates an HttpError that carries an underlying cause. StatusCode() returns code, Error() returns the HTTP status text followed by the cause detail, and errors.Unwrap traverses to cause. For standard HTTP codes with a nil cause, Error() matches NewHttpError(code). For unrecognised codes where http.StatusText returns an empty string, Error() falls back to "HTTP <code>" so the string is never empty — this diverges from NewHttpError(code), which returns "" for such codes; switching from NewHttpError to WrapHttpError(code, nil) on a bogus code therefore changes the Error() output.

type LeveledHandler added in v1.0.6

type LeveledHandler interface {
	io.Writer
	MinLevel() LogLevel
}

LeveledHandler is an optional interface a handler may implement to declare a minimum log level distinct from the parent Logger's minimumLogLevel. When a handler implements LeveledHandler, Logger.WriteLog uses its MinLevel() instead of Logger.minimumLogLevel for the per-handler gate. Handlers that do not implement LeveledHandler keep the historical behaviour — they fire when level >= Logger.minimumLogLevel.

MinLevel must be safe to call concurrently and must return the same value for the lifetime of the handler — Logger caches no result.

type LogLevel

type LogLevel int

LogLevel is a log level

type Logger

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

Logger is describing the logger structure and its methods

func NewFileLogger added in v1.0.6

func NewFileLogger(folder, prefix string, minLevel LogLevel, opts ...FileLoggerOption) (*Logger, error)

NewFileLogger builds a Logger that writes to size-rotated files under folder and, by default, mirrors every message at or above minLevel to a timestamped stdout printer (TimestampedPrintHandler). The printer fires as a handler (level >= minLevel), not a hook, so its gating matches the file handler exactly.

File lines are timestamped: the rotated file handler is wrapped in TimestampedHandler, so each on-disk line is "<timestamp> <message>" (layout "2006/01/02 15:04:05"), not the bare message. The console printer and the file handler are two separate sinks, each stamped exactly once.

The standard library log package is NOT redirected unless WithStdLogRedirect is passed — that redirect mutates global process state. An empty folder is rejected unless WithTempDirFallback is passed, in which case logs land under filepath.Join(os.TempDir(), "logs"). An empty prefix is always rejected because a hidden dotfile log (prefix ".") is a footgun.

Defaults: maxFileCapacity = 5 MiB, maxFilesInFolder = 7, printer ON, std-log redirect OFF, temp fallback OFF.

func NewLogger

func NewLogger(min LogLevel, handlers ...io.Writer) *Logger

NewLogger creates a new logger with the given minimum log level and handlers. If no handlers are provided, a default TimestampedPrintHandler (timestamped stdout) is added. Each handler is wrapped so the logger never calls its Write concurrently, which makes logging safe from multiple goroutines even with non-thread-safe handlers.

func (*Logger) Hook

func (l *Logger) Hook(writer io.Writer, level LogLevel, additional ...LogLevel) HookID

Hook registers writer to fire on an EXACT match of any of the given levels (level plus additional), regardless of the logger's minimum level. Duplicate levels are collapsed to a single registration, so Hook(w, X, X) writes to w once per matching WriteLog, not twice. The returned HookID removes every registration created by this call when passed to Unhook.

Hooks match on exact equality, not a threshold: a hook on level X does NOT fire for a message logged at level X+1. To alert on "level >= X" — the common case for routing high-severity messages to an out-of-band sink — register a WithMinLevel(X, writer) handler instead of enumerating exact levels, because an enumeration that omits one level silently drops that level's messages.

The returned HookID is unique only within this logger; do not persist or compare it against a fixed format.

func (*Logger) Panic added in v1.0.6

func (l *Logger) Panic(level LogLevel, args ...any)

Panic writes a log message at the given level and then panics with the message string. The panic is recoverable: deferred functions run and callers may use recover() to catch it. It does NOT call os.Exit — process death is not guaranteed.

func (*Logger) Panicf added in v1.0.6

func (l *Logger) Panicf(level LogLevel, format string, args ...any)

Panicf writes a formatted log message at the given level and then panics with the message string. The panic is recoverable: deferred functions run and callers may use recover() to catch it. It does NOT call os.Exit — process death is not guaranteed.

func (*Logger) Print

func (l *Logger) Print(level LogLevel, args ...any)

Print writes a log message

func (*Logger) Printf

func (l *Logger) Printf(level LogLevel, format string, args ...any)

Printf writes a formatted log message

func (*Logger) SetMinLevel

func (l *Logger) SetMinLevel(level LogLevel)

SetMinLevel sets the minimum log level

func (*Logger) StdLog added in v1.0.6

func (l *Logger) StdLog(level LogLevel, prefix string) *log.Logger

StdLog returns a standard-library *log.Logger that writes through this logger at the given level, with the given prefix. It is configured with log.Lmsgprefix and no date/time flags: the emitter contributes only the prefix, and any timestamp is added by the sink (e.g. TimestampedPrintHandler or TimestampedHandler), so a line is never stamped twice. Use it to hand a *log.Logger to code that expects one — for example a database driver's logger — without hand-wiring the flags:

dbLog := logger.StdLog(levels.Info, "sqlite ")

Each call returns a fresh *log.Logger over a fresh writer; the returned logger routes through WriteLog, which serializes writes per sink.

func (*Logger) Unhook

func (l *Logger) Unhook(id HookID)

Unhook removes a hook from the logger

func (*Logger) Write

func (l *Logger) Write(message []byte) (int, error)

Write writes a log message with the minimum log level

func (*Logger) WriteLog

func (l *Logger) WriteLog(level LogLevel, message []byte) (int, error)

WriteLog writes a log message at the given level to all matching sinks.

Hooks fire on an exact level match regardless of minimumLogLevel. Handlers fire only when level >= minimumLogLevel. When no sinks match (below the minimum and no matching hooks), WriteLog returns (0, nil). When exactly one sink matches it is written inline (no goroutine overhead). Two or more sinks run concurrently, each in its own goroutine, with errors joined via errors.Join.

WriteLog holds the read lock for the entire duration so it is safe to call concurrently with Hook/Unhook/SetMinLevel.

func (*Logger) WriterAs

func (l *Logger) WriterAs(level LogLevel) io.Writer

WriterAs returns a writer that writes to the logger as the given log level

type PrintOption added in v1.0.6

type PrintOption func(*printConfig)

PrintOption configures TimestampedPrintHandler.

func WithOutput added in v1.0.6

func WithOutput(w io.Writer) PrintOption

WithOutput overrides the sink used by TimestampedPrintHandler. The default is os.Stdout. This is primarily intended to redirect output for testing, but it is a legitimate consumer need (e.g. writing timestamped messages to a file instead of stdout).

func WithTimeLayout added in v1.0.6

func WithTimeLayout(layout string) PrintOption

WithTimeLayout overrides the timestamp layout used by TimestampedPrintHandler. The default layout is "2006/01/02 15:04:05".

type PublicDetailsError added in v1.0.6

type PublicDetailsError interface {
	PublicError
	// Details returns the joined client-safe text. It is an alias of
	// PublicMessage and always returns the same value.
	Details() string
}

PublicDetailsError extends PublicError with a Details accessor that returns the same client-safe text as PublicMessage. It is the return type of NewPublicErrorDetails and exists so callers using the variadic constructor have an accessor name that matches the common consumer convention.

func NewPublicErrorDetails added in v1.0.6

func NewPublicErrorDetails(parts ...string) PublicDetailsError

NewPublicErrorDetails creates a PublicDetailsError by joining the given parts with a single space. The result has no internal cause; Error() and PublicMessage() both return the joined string. It is a convenience for the common pattern of building a public message from several parts and is semantically identical to NewPublicError(strings.Join(parts, " "), nil).

type PublicError added in v1.0.6

type PublicError interface {
	// PublicMessage returns the client-safe text. It never includes any detail
	// from the internal cause, even if the public string is empty.
	PublicMessage() string
	error
}

PublicError separates a user-safe public message from the internal cause. Error() returns the internal detail for logging; PublicMessage() returns only the text that is safe to expose to clients and never leaks the internal cause.

func NewPublicError added in v1.0.6

func NewPublicError(public string, cause error) PublicError

NewPublicError creates a PublicError from a client-safe message and an internal cause. Error() returns the cause's error text for logging, falling back to the public message only when cause is nil. PublicMessage() always returns only the public string. Unwrap returns cause, so errors.Is and errors.As traverse to it.

type RotatingFileOption added in v1.0.6

type RotatingFileOption func(*rotatingFileConfig)

RotatingFileOption configures RotatingFileHandler.

func WithFreshStart added in v1.0.6

func WithFreshStart() RotatingFileOption

WithFreshStart makes the handler remove every existing prefix.<8 hex>.log file in folder once at construction, before the first write, so each process run begins with a genuinely empty ring at index 1. Existing rotated content is permanently destroyed — this is the intended behaviour; omit the option to resume and preserve prior content across restarts. Files in folder that do not match the prefix.<8 hex>.log shape are left untouched.

A missing file or folder is a no-op: no file is created at construction, and the first write creates index-1. Any removal error is stashed and surfaced on the first Write's return value, then cleared. WithFreshStart forces the starting index to 1 and the seeded size to 0, overriding the resume-at-highest-index behaviour.

func WithMaxFileSize added in v1.0.6

func WithMaxFileSize(n uint32) RotatingFileOption

WithMaxFileSize overrides the maximum size of a single log file in bytes before the handler rotates to the next index. The default is 5 MiB (5 << 20).

func WithMaxFiles added in v1.0.6

func WithMaxFiles(n int) RotatingFileOption

WithMaxFiles overrides the maximum number of rotated log files retained in the folder. Older files beyond the limit are pruned on rotation. The default is 7.

type StackTraceError added in v1.0.6

type StackTraceError interface {
	// Stack returns the full debug.Stack() output captured at construction time.
	// The output contains absolute build-host file paths — do not forward it to
	// untrusted clients or HTTP responses.
	Stack() string
	// Runtime returns a string describing the OS, architecture, and Go version,
	// computed once on first use and cached for the process lifetime — every
	// StackTraceError value shares the same string. If
	// SetRuntimeDetailsProvider was called with a non-nil function before any
	// StackTraceError was constructed, the provider's output is appended to the
	// built-in os/arch/go descriptor.
	Runtime() string
	error
}

StackTraceError captures a full stack trace and runtime environment details at the point of construction. Error() returns only the message and cause for compact log lines; the full stack is available on demand via Stack(). Runtime details (OS, arch, Go version) are cached once for the process lifetime.

func NewStackTraceError added in v1.0.6

func NewStackTraceError() StackTraceError

NewStackTraceError creates a StackTraceError with the full call-stack captured at this call site. Error() returns an empty string because no message is supplied — call Stack() for the full trace. The empty Error() means this value contributes no text when joined via errors.Join or wrapped via fmt.Errorf("%w"); use NewStackTraceErrorf when you need a non-empty error string in the chain. Stack() contains absolute build-host file paths; do not forward it to untrusted clients.

func NewStackTraceErrorf added in v1.0.6

func NewStackTraceErrorf(format string, args ...any) StackTraceError

NewStackTraceErrorf creates a StackTraceError with a formatted message and the full call-stack captured at this call site. Error() returns the message only; use Stack() for the full trace.

func WrapStackTraceError added in v1.0.6

func WrapStackTraceError(cause error, format string, args ...any) StackTraceError

WrapStackTraceError creates a StackTraceError with a formatted message, the full call-stack captured at this call site, and an explicit cause. errors.Unwrap, errors.Is, and errors.As traverse to cause. Passing a nil cause is safe. The message is rendered with fmt.Sprintf — do NOT use %w in the format string; use the cause argument for wrapping.

type TraceError

type TraceError interface {
	Line() string
	error
}

TraceError is an error that provides a stack trace.

func NewTraceError

func NewTraceError() TraceError

NewTraceError creates a new TraceError with the current stack trace.

func NewTraceErrorf added in v1.0.6

func NewTraceErrorf(format string, args ...any) TraceError

NewTraceErrorf creates a TraceError with a formatted message and the current call-site line. The message is built via fmt.Errorf, so a %w verb in format wraps the corresponding argument and errors.Is/errors.As traverse to it (through one intermediate fmt.Errorf node — Unwrap is always non-nil for the value produced here; it just terminates with nil when no %w was used). To have Unwrap return your cause directly with a single hop, use WrapTraceError. The stack line is captured at the point of this call, not inside any helper.

func WrapTraceError added in v1.0.6

func WrapTraceError(cause error, format string, args ...any) TraceError

WrapTraceError creates a TraceError with a formatted message, the current call-site line, and an explicit cause. errors.Unwrap, errors.Is, and errors.As traverse to cause. Passing a nil cause is safe; Unwrap returns nil. The message is rendered with fmt.Sprintf — do NOT use %w in the format string, it would produce a "%!w(...)" garbage token; rely on the cause argument for wrapping, or use NewTraceErrorf if you need %w in the format itself.

Directories

Path Synopsis
Package httptap is the opt-in HTTP-traffic tap for github.com/prorochestvo/loginjector.
Package httptap is the opt-in HTTP-traffic tap for github.com/prorochestvo/loginjector.
synctest
Package synctest provides a mutex-guarded bytes.Buffer for the loginjector test suites.
Package synctest provides a mutex-guarded bytes.Buffer for the loginjector test suites.
Package levels provides an optional preset of named severity constants for the loginjector.LogLevel type.
Package levels provides an optional preset of named severity constants for the loginjector.LogLevel type.

Jump to

Keyboard shortcuts

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