7 Commits

Author SHA1 Message Date
clawbot
a5b4c2af0d feat: split Dockerfile into dedicated lint stage
Pin all images by sha256 digest and restructure for the standard
split-stage pattern.

- Lint stage: golangci/golangci-lint:v1.64.8 pinned by sha256
- Test stage: golang:1.22 pinned by sha256, depends on lint via COPY
- Final stage: golang:1.22 pinned by sha256
- COPY --from=lint forces BuildKit to execute the lint stage
2026-03-02 00:03:04 -08:00
9121da9aae Merge pull request 'fix: JSONHandler deadlock from recursive log.Println (closes #3)' (#4) from clawbot/simplelog:fix/json-handler-deadlock into main
Reviewed-on: #4
2026-02-08 18:29:55 +01:00
74ce052b77 Merge branch 'main' into fix/json-handler-deadlock 2026-02-08 18:29:12 +01:00
1eef38a5fa Merge pull request 'test: add deadlock regression test for JSONHandler (issue #3)' (#7) from clawbot/simplelog:test/jsonhandler-deadlock into main
Reviewed-on: #7
2026-02-08 18:27:15 +01:00
97a82e9b2c test: add deadlock regression test for JSONHandler
Reproduces issue #3 — JSONHandler.Handle() calling log.Println() causes
a deadlock when slog.SetDefault redirects log output back through slog.

This test hangs/fails on main and should pass once #4 is merged.
2026-02-08 09:21:08 -08:00
user
869b7ca4c3 fix: replace log.Println with fmt.Fprintln in JSONHandler to prevent deadlock 2026-02-08 09:15:17 -08:00
31c9ed52cb preparing for 1.0 2024-06-14 05:53:22 -07:00
5 changed files with 79 additions and 34 deletions

View File

@@ -1,39 +1,27 @@
# First stage: Use the golangci-lint image to run the linter # Lint stage — fast feedback on formatting and lint issues
FROM golangci/golangci-lint:latest as lint # golangci/golangci-lint:v1.64.8
FROM golangci/golangci-lint@sha256:2987913e27f4eca9c8a39129d2c7bc1e74fbcf77f181e01cea607be437aa5cb8 AS lint
# Set the Current Working Directory inside the container WORKDIR /src
WORKDIR /app COPY go.mod go.sum ./
RUN go mod download
# Copy the go.mod file and the rest of the application code
COPY go.mod ./
COPY . . COPY . .
# Run golangci-lint
RUN golangci-lint run RUN golangci-lint run
RUN sh -c 'test -z "$(gofmt -l .)"' RUN sh -c 'test -z "$(gofmt -l .)"'
# Second stage: Use the official Golang image to run tests # Test stage run tests
FROM golang:1.22 as test # golang:1.22
FROM golang@sha256:1cf6c45ba39db9fd6db16922041d074a63c935556a05c5ccb62d181034df7f02 AS test
# Set the Current Working Directory inside the container WORKDIR /src
WORKDIR /app # Force BuildKit to run the lint stage by creating a stage dependency
COPY --from=lint /src/go.sum /dev/null
# Copy the go.mod file and the rest of the application code COPY go.mod go.sum ./
COPY go.mod ./ RUN go mod download
COPY . . COPY . .
# Run tests
RUN go test -v ./... RUN go test -v ./...
# Final stage: Combine the linting and testing stages # Final stage: Combine the linting and testing stages
FROM golang:1.22 as final # golang:1.22
FROM golang@sha256:1cf6c45ba39db9fd6db16922041d074a63c935556a05c5ccb62d181034df7f02 AS final
# Ensure that the linting stage succeeded
WORKDIR /app WORKDIR /app
COPY --from=lint /app . COPY --from=test /src/go.mod ./
COPY --from=test /app .
# Set the final CMD to something minimal since we only needed to verify lint and tests during build
CMD ["echo", "Build and tests passed successfully!"] CMD ["echo", "Build and tests passed successfully!"]

14
LICENSE Normal file
View File

@@ -0,0 +1,14 @@
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.

View File

@@ -11,15 +11,18 @@ stdlib `log/slog` default handler, and solve the 90% case for logging.
## Current Status ## Current Status
Pre-1.0, not working yet. Released v1.0.0 2024-06-14. Works as intended. No known bugs.
## Features ## Features
- if output is a tty, outputs pretty color logs - if output is a tty, outputs pretty color logs
- if output is not a tty, outputs json - if output is not a tty, outputs json
- supports delivering logs via tcp RELP (e.g. to remote rsyslog using imrelp)
- supports delivering each log message via a webhook - supports delivering each log message via a webhook
## Planned Features
- supports delivering logs via tcp RELP (e.g. to remote rsyslog using imrelp)
## Installation ## Installation
To use simplelog, first ensure your project is set up with Go modules: To use simplelog, first ensure your project is set up with Go modules:
@@ -36,7 +39,9 @@ go get sneak.berlin/go/simplelog
## Usage ## Usage
Below is an example of how to use SimpleLog in a Go application. This example is provided in the form of a `main.go` file, which demonstrates logging at various levels using structured logging syntax. Below is an example of how to use SimpleLog in a Go application. This
example is provided in the form of a `main.go` file, which demonstrates
logging at various levels using structured logging syntax.
```go ```go
package main package main
@@ -54,3 +59,7 @@ func main() {
slog.Error("Failed to save data", slog.String("reason", "permission denied")) slog.Error("Failed to save data", slog.String("reason", "permission denied"))
} }
``` ```
## License
[WTFPL](./LICENSE)

View File

@@ -3,8 +3,9 @@ package simplelog
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"log" "fmt"
"log/slog" "log/slog"
"os"
) )
type JSONHandler struct{} type JSONHandler struct{}
@@ -15,7 +16,7 @@ func NewJSONHandler() *JSONHandler {
func (j *JSONHandler) Handle(ctx context.Context, record slog.Record) error { func (j *JSONHandler) Handle(ctx context.Context, record slog.Record) error {
jsonData, _ := json.Marshal(record) jsonData, _ := json.Marshal(record)
log.Println(string(jsonData)) fmt.Fprintln(os.Stdout, string(jsonData))
return nil return nil
} }

33
json_handler_test.go Normal file
View File

@@ -0,0 +1,33 @@
package simplelog
import (
"log/slog"
"testing"
"time"
)
// TestJSONHandlerDeadlock verifies that JSONHandler.Handle does not deadlock
// when the default slog handler routes log.Println back through slog.
// On the unfixed code this test will hang (deadlock); with the fix it completes.
func TestJSONHandlerDeadlock(t *testing.T) {
handler := NewJSONHandler()
// Set our handler as the default so log.Println routes through slog
logger := slog.New(handler)
slog.SetDefault(logger)
done := make(chan struct{})
go func() {
// This call deadlocks on unfixed code because Handle() calls
// log.Println() which re-enters slog → Handle() → log.Println() …
slog.Info("test message")
close(done)
}()
select {
case <-done:
// success
case <-time.After(5 * time.Second):
t.Fatal("JSONHandler.Handle deadlocked: timed out after 5 seconds")
}
}