Skip to content

Commit dafbff9

Browse files
authored
feat(log): Provided module (#3)
1 parent 06c796f commit dafbff9

25 files changed

+1798
-11
lines changed

.github/workflows/common-ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ on:
66
module:
77
required: false
88
type: string
9-
default: '.'
9+
default: "."
1010
go_version:
1111
required: false
1212
type: string
13-
default: '1.20.0'
13+
default: "1.20.0"
1414

1515
permissions:
1616
contents: read

.github/workflows/coverage.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ on:
99
go_version:
1010
required: false
1111
type: string
12-
default: '1.20.0'
12+
default: "1.20.0"
1313

1414
permissions:
1515
contents: write
@@ -21,6 +21,7 @@ jobs:
2121
matrix:
2222
module:
2323
- "config"
24+
- "log"
2425

2526
runs-on: ubuntu-latest
2627
steps:

.github/workflows/log-ci.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: "log-ci"
2+
3+
on:
4+
push:
5+
branches:
6+
- "feat**"
7+
- "fix**"
8+
- "hotfix**"
9+
- "chore**"
10+
paths:
11+
- "log/**.go"
12+
- "log/go.mod"
13+
- "log/go.sum"
14+
pull_request:
15+
types:
16+
- opened
17+
- synchronize
18+
- reopened
19+
branches:
20+
- main
21+
paths:
22+
- "log/**.go"
23+
- "log/go.mod"
24+
- "log/go.sum"
25+
26+
jobs:
27+
ci:
28+
uses: ./.github/workflows/common-ci.yml
29+
secrets: inherit
30+
with:
31+
module: "log"

README.md

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Yokai
22

3+
[![Go version](https://img.shields.io/badge/go-%3E%3D1.20-blue)](https://go.dev/)
4+
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5+
36
> Simple, modular, and observable Go framework.
47
58
<p align="center">
@@ -12,14 +15,14 @@ Yokai's documentation will be available soon.
1215

1316
## Modules
1417

15-
| Module | Description |
16-
|------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------|
17-
| [config](modules/config) | Config module based on [Viper](https://github.com/spf13/viper) |
18-
| [generate](modules/generate) | Generation module based on [Google UUID](https://github.com/google/uuid) |
19-
| [healthcheck](modules/healthcheck) | Health check module compatible with [K8s probes](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) |
20-
| [httpclient](modules/httpclient) | Http client module based on [net/http](https://pkg.go.dev/net/http) |
21-
| [log](modules/log) | Logging module based on [Zerolog](https://github.com/rs/zerolog) |
22-
| [trace](modules/trace) | Tracing module based on [OpenTelemetry](https://github.com/open-telemetry/opentelemetry-go) |
18+
| Module | Description |
19+
|----------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------|
20+
| [config](config) | Config module based on [Viper](https://github.com/spf13/viper) |
21+
| [generate](generate) | Generation module based on [Google UUID](https://github.com/google/uuid) |
22+
| [healthcheck](healthcheck) | Health check module compatible with [K8s probes](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) |
23+
| [httpclient](httpclient) | Http client module based on [net/http](https://pkg.go.dev/net/http) |
24+
| [log](log) | Logging module based on [Zerolog](https://github.com/rs/zerolog) |
25+
| [trace](trace) | Tracing module based on [OpenTelemetry](https://github.com/open-telemetry/opentelemetry-go) |
2326

2427
## Contributing
2528

log/.golangci.yml

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
run:
2+
timeout: 5m
3+
concurrency: 8
4+
5+
linters:
6+
enable:
7+
- asasalint
8+
- asciicheck
9+
- bidichk
10+
- bodyclose
11+
- containedctx
12+
- contextcheck
13+
- cyclop
14+
- decorder
15+
- dogsled
16+
- durationcheck
17+
- errcheck
18+
- errchkjson
19+
- errname
20+
- errorlint
21+
- exhaustive
22+
- forbidigo
23+
- forcetypeassert
24+
- gocognit
25+
- goconst
26+
- gocritic
27+
- gocyclo
28+
- godot
29+
- godox
30+
- gofmt
31+
- goheader
32+
- gomoddirectives
33+
- gomodguard
34+
- goprintffuncname
35+
- gosec
36+
- gosimple
37+
- govet
38+
- grouper
39+
- importas
40+
- ineffassign
41+
- interfacebloat
42+
- logrlint
43+
- maintidx
44+
- makezero
45+
- misspell
46+
- nestif
47+
- nilerr
48+
- nilnil
49+
- nlreturn
50+
- nolintlint
51+
- nosprintfhostport
52+
- prealloc
53+
- predeclared
54+
- promlinter
55+
- reassign
56+
- staticcheck
57+
- tenv
58+
- thelper
59+
- tparallel
60+
- typecheck
61+
- unconvert
62+
- unparam
63+
- unused
64+
- usestdlibvars
65+
- whitespace

log/README.md

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
# Log Module
2+
3+
[![ci](https://github.com/ankorstore/yokai/actions/workflows/log-ci.yml/badge.svg)](https://github.com/ankorstore/yokai/actions/workflows/log-ci.yml)
4+
[![go report](https://goreportcard.com/badge/github.com/ankorstore/yokai/log)](https://goreportcard.com/report/github.com/ankorstore/yokai/log)
5+
[![codecov](https://codecov.io/gh/ankorstore/yokai/graph/badge.svg?token=5s0g5WyseS&flag=log)](https://app.codecov.io/gh/ankorstore/yokai/tree/main/log)
6+
[![PkgGoDev](https://pkg.go.dev/badge/github.com/ankorstore/yokai/log)](https://pkg.go.dev/github.com/ankorstore/yokai/log)
7+
8+
> Logging module based on [Zerolog](https://github.com/rs/zerolog).
9+
10+
<!-- TOC -->
11+
* [Installation](#installation)
12+
* [Documentation](#documentation)
13+
* [Usage](#usage)
14+
* [Context](#context)
15+
* [Testing](#testing)
16+
<!-- TOC -->
17+
18+
## Installation
19+
20+
```shell
21+
go get github.com/ankorstore/yokai/log
22+
```
23+
24+
## Documentation
25+
26+
This module provides a [Logger](logger.go), offering all [Zerolog](https://github.com/rs/zerolog) methods.
27+
28+
### Usage
29+
30+
To create a `Logger`:
31+
32+
```go
33+
package main
34+
35+
import (
36+
"os"
37+
38+
"github.com/ankorstore/yokai/log"
39+
"github.com/rs/zerolog"
40+
)
41+
42+
var logger, _ = log.NewDefaultLoggerFactory().Create()
43+
44+
// equivalent to:
45+
var logger, _ = log.NewDefaultLoggerFactory().Create(
46+
log.WithServiceName("default"), // adds {"service":"default"} to log records
47+
log.WithLevel(zerolog.InfoLevel), // logs records with level >= info
48+
log.WithOutputWriter(os.Stdout), // sends logs records to stdout
49+
)
50+
```
51+
52+
To use the `Logger`:
53+
54+
```go
55+
package main
56+
57+
import (
58+
"github.com/ankorstore/yokai/log"
59+
)
60+
61+
func main() {
62+
logger, _ := log.NewDefaultLoggerFactory().Create()
63+
64+
logger.Info().Msg("some message") // {"level:"info", "service":"default", "message":"some message"}
65+
}
66+
```
67+
68+
See [Zerolog](https://github.com/rs/zerolog) documentation for more details about available methods.
69+
70+
### Context
71+
72+
This module provides the `log.CtxLogger()` function that allow to extract the logger from a `context.Context`.
73+
74+
If no logger is found in context, a [default](https://github.com/rs/zerolog/blob/master/ctx.go) Zerolog based logger will be used.
75+
76+
### Testing
77+
78+
This module provides a [TestLogBuffer](logtest/buffer.go), recording log records to be able to assert on them after logging:
79+
80+
```go
81+
package main
82+
83+
import (
84+
"fmt"
85+
86+
"github.com/ankorstore/yokai/log"
87+
"github.com/ankorstore/yokai/log/logtest"
88+
)
89+
90+
func main() {
91+
buffer := logtest.NewDefaultTestLogBuffer()
92+
93+
logger, _ := log.NewDefaultLoggerFactory().Create(log.WithOutputWriter(buffer))
94+
95+
logger.Info().Msg("some message example")
96+
97+
// test on attributes exact matching
98+
hasRecord, _ := buffer.HasRecord(map[string]interface{}{
99+
"level": "info",
100+
"message": "some message example",
101+
})
102+
103+
fmt.Printf("has record: %v", hasRecord) // has record: true
104+
105+
// test on attributes partial matching
106+
containRecord, _ := buffer.ContainRecord(map[string]interface{}{
107+
"level": "info",
108+
"message": "message",
109+
})
110+
111+
fmt.Printf("contain record: %v", containRecord) // contain record: true
112+
}
113+
```
114+
115+
You can also use the provided [test assertion helpers](logtest/assert.go) in your tests:
116+
- `AssertHasLogRecord`: to assert on exact attributes match
117+
- `AssertHasNotLogRecord`: to assert on exact attributes non match
118+
- `AssertContainLogRecord`: to assert on partial attributes match
119+
- `AssertContainNotLogRecord`: to assert on partial attributes non match
120+
121+
```go
122+
package main_test
123+
124+
import (
125+
"fmt"
126+
"testing"
127+
128+
"github.com/ankorstore/yokai/log"
129+
"github.com/ankorstore/yokai/log/logtest"
130+
)
131+
132+
func TestLogger(t *testing.T) {
133+
buffer := logtest.NewDefaultTestLogBuffer()
134+
135+
logger, _ := log.NewDefaultLoggerFactory().Create(log.WithOutputWriter(buffer))
136+
137+
logger.Info().Msg("some message example")
138+
139+
// assertion success
140+
logtest.AssertHasLogRecord(t, buffer, map[string]interface{}{
141+
"level": "info",
142+
"message": "some message example",
143+
})
144+
145+
// assertion success
146+
logtest.AssertHasNotLogRecord(t, buffer, map[string]interface{}{
147+
"level": "info",
148+
"message": "some invalid example",
149+
})
150+
151+
// assertion success
152+
logtest.AssertContainLogRecord(t, buffer, map[string]interface{}{
153+
"level": "info",
154+
"message": "message",
155+
})
156+
157+
// assertion success
158+
logtest.AssertContainNotLogRecord(t, buffer, map[string]interface{}{
159+
"level": "info",
160+
"message": "invalid",
161+
})
162+
}
163+
```

log/context.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package log
2+
3+
import (
4+
"context"
5+
6+
"github.com/rs/zerolog"
7+
"go.opentelemetry.io/otel/trace"
8+
)
9+
10+
// CtxLogger retrieves a [Logger] from a provided context (or creates and appends a new one if missing).
11+
//
12+
// It automatically adds the traceID and spanID log fields depending on current tracing context.
13+
func CtxLogger(ctx context.Context) *Logger {
14+
fields := make(map[string]interface{})
15+
16+
spanContext := trace.SpanContextFromContext(ctx)
17+
if spanContext.HasTraceID() {
18+
fields["traceID"] = spanContext.TraceID().String()
19+
}
20+
if spanContext.HasSpanID() {
21+
fields["spanID"] = spanContext.SpanID().String()
22+
}
23+
24+
if len(fields) > 0 {
25+
logger := zerolog.Ctx(ctx).With().Fields(fields).Logger()
26+
27+
return &Logger{&logger}
28+
}
29+
30+
return &Logger{zerolog.Ctx(ctx)}
31+
}

0 commit comments

Comments
 (0)