Compare commits

..

19 commits
v0.2.1 ... main

Author SHA1 Message Date
1d7177fb26
feat: new priority key settings for key value formatter
All checks were successful
Build / Checks (push) Successful in 31s
Build / Code Coverage (push) Successful in 43s
Build / Build (push) Successful in 31s
Release / Semantic Release (push) Successful in 36s
2025-03-05 17:48:47 +01:00
f44e60cca4
style: replaced old interface{} with new any type alias 2025-03-05 17:47:49 +01:00
5d174f8685
style: removed empty new line 2025-03-05 17:47:17 +01:00
a2883c1077
feat: new map type alias 2025-03-05 17:46:59 +01:00
ba2ba315d0
docs: enhanced simple example
All checks were successful
Build / Checks (push) Successful in 33s
Build / Code Coverage (push) Successful in 43s
Build / Build (push) Successful in 30s
Release / Semantic Release (push) Successful in 45s
2025-03-04 20:56:50 +01:00
a1c991bcbd
style: fix goland warnings for unused parameters 2025-03-04 20:56:30 +01:00
eb8b9abe1a
feat: new WithMap method for adding multiple values with one call 2025-03-04 20:56:00 +01:00
56b1e3612b
ci: new build workflow
All checks were successful
Build / Checks (push) Successful in 34s
Build / Code Coverage (push) Successful in 49s
Build / Build (push) Successful in 36s
Release / Semantic Release (push) Successful in 41s
2025-02-28 16:13:23 +01:00
3e9453bf56
ci: new semantic release workflow 2025-02-28 16:07:04 +01:00
ea862ef834
ci: use new renovate config location 2025-02-28 16:02:39 +01:00
794f3ed498 chore: Configure Renovate (#1)
Reviewed-on: #1
2025-02-24 19:13:03 +00:00
12214121c2
fix(Formatter): reset KeyValue formatter for each new entry 2025-02-20 18:43:41 +01:00
0819e83691
refactor: renamed golog to log 2025-02-20 18:06:56 +01:00
b44771fddb
fix: removed debug log 2024-05-25 16:33:06 +02:00
2a3fcd390a
feat: new sample app 2024-05-25 16:32:57 +02:00
4286b9e5ce
ignore jetbrains idea folder 2024-05-25 16:32:46 +02:00
ff82c54533
fix: escaping of : and \ in keyvalue formatter 2024-01-04 15:15:35 +01:00
a98708a4a9
feat: add caller information 2024-01-04 15:14:54 +01:00
6353cb045a
fix: add new line after each log entry 2024-01-04 14:46:23 +01:00
20 changed files with 389 additions and 35 deletions

View file

@ -0,0 +1,81 @@
name: Build
on:
push:
branches:
- main
- beta
- develop
pull_request:
jobs:
checks:
name: Checks
runs-on: docker
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.23.x'
check-latest: true
- name: Run go fmt and go vet
run: |
go fmt $(go list ./...)
go vet $(go list ./...)
code-coverage:
name: Code Coverage
runs-on: docker
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.23.x'
check-latest: true
- name: Run tests and generate coverage report
run: |
go test -covermode=count -coverprofile coverage.cov $(go list ./...)
go tool cover -func=coverage.cov
go tool cover -html=coverage.cov -o coverage.html
- name: Upload coverage artifacts
uses: https://code.forgejo.org/forgejo/upload-artifact@v4
with:
name: coverage-reports
path: |
coverage.cov
coverage.html
build:
name: Build
runs-on: docker
strategy:
matrix:
go:
- GOOS: darwin
GOARCH: amd64
- GOOS: darwin
GOARCH: arm64
- GOOS: linux
GOARCH: amd64
- GOOS: linux
GOARCH: arm64
- GOOS: windows
GOARCH: amd64
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.23.x'
check-latest: true
- name: Set environment variables
run: |
echo "GOOS=${{ matrix.go.GOOS }}" >> $GITHUB_ENV
echo "GOARCH=${{ matrix.go.GOARCH }}" >> $GITHUB_ENV
- name: Build
run: go build .

View file

@ -0,0 +1,20 @@
name: Release
on:
push:
branches:
- main
- beta
jobs:
release:
name: Semantic Release
runs-on: docker
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Semantic Release
shell: bash
run: |
npm install -g semantic-release@23 conventional-changelog-conventionalcommits@7
semantic-release

1
.gitignore vendored
View file

@ -1,2 +1,3 @@
.DS_Store
.idea
.vscode

72
.releaserc Normal file
View file

@ -0,0 +1,72 @@
{
"branches": [
"main",
{
"name": "beta",
"prerelease": true
}
],
"plugins": [
[
"@semantic-release/commit-analyzer",
{
"preset": "conventionalcommits",
"releaseRules": [
{
"type": "chore",
"release": "patch"
},
{
"type": "build",
"release": "patch"
},
{
"type": "ci",
"release": "patch"
}
]
}
],
[
"@semantic-release/release-notes-generator",
{
"preset": "conventionalcommits",
"presetConfig": {
"types": [
{
"type": "feat",
"section": "Features"
},
{
"type": "feature",
"section": "Features"
},
{
"type": "fix",
"section": "Bug Fixes"
},
{
"type": "perf",
"section": "Performance Improvements"
},
{
"type": "revert",
"section": "Reverts"
},
{
"type": "chore",
"section": "Miscellaneous Chores"
}
]
}
}
],
[
"@semantic-release/github",
{
"successCommentCondition": false,
"failTitle": false
}
]
]
}

View file

@ -1,23 +1,25 @@
# goLog
# log
GoLog is a simple logging framework for golang.
Log is a simple logging framework for golang.
## Getting Started
```golang
import log "gitlab.com/martinr92/golog"
import "git.martin-riedl.de/golang/log"
func main(){
log.Default.Info("My First Info Message")
}
```
Check out this [simple example file](cmd/simple/main.go) for the basic usage.
## Multiple Log Instance
Create a new log instance (instead of using the `Default`).
```golang
import log "gitlab.com/martinr92/golog"
import "git.martin-riedl.de/golang/log"
var myLog := log.NewLoggerDefault()
myLog.Info("My First Info Message")
@ -32,7 +34,7 @@ myLog.Info("My First Info Message")
The following sample creates 2 outputs. The first sends a key/value format to stdout. The second writes messages as JSON format to stdout.
```golang
import log "gitlab.com/martinr92/golog"
import "git.martin-riedl.de/golang/log"
var output1 := NewOutput(log.LevelInfo, NewFormatterKeyValue(), NewPrinterStdout()
var output2 := NewOutput(log.LevelError, NewFormatterJSON(), NewPrinterStdout())
@ -41,7 +43,6 @@ var myLog := log.NewLogger(
[]*Output{output1, output2}
)
myLog.Info("My First Info Message")
```
## Custom Formatter / Printer

View file

@ -0,0 +1,30 @@
package main
import (
"time"
"git.martin-riedl.de/golang/log"
)
func main() {
advancedSettings()
}
func advancedSettings() {
// Key Value Formatter with special settings
formatter := log.NewFormatterKeyValue()
formatter.HighPriorityKeys = []string{"firstField"}
formatter.PriorityKeys = []string{"secondField"}
formatter.TimeFormat = time.RFC3339Nano
// create new log instance
output := log.NewOutput(log.LevelDebug, formatter, log.NewPrinterStdout())
logger := log.NewLogger([]*log.Output{output})
// log some message
logger.WithMap(log.Map{
"someField": "Martin",
"firstField": "John",
"secondField": "Doe",
}).Info("This is an info message")
}

24
cmd/simple/main.go Normal file
View file

@ -0,0 +1,24 @@
package main
import (
"runtime"
"git.martin-riedl.de/golang/log"
)
func main() {
someMethod()
anotherMethod()
}
func someMethod() {
log.Default.Info("Hello World")
log.Default.With("os", runtime.GOOS).Warning("environment detected")
}
func anotherMethod() {
log.Default.WithMap(log.Map{
"foo": "bar",
"bar": "baz",
}).Info("Second Hello World")
}

View file

@ -12,19 +12,21 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package golog
package log
import (
"fmt"
"runtime"
"time"
)
type Entry struct {
Logger *Logger
Content []Content
Message string
Level Level
Time time.Time
Logger *Logger
Content []Content
Message string
Level Level
Time time.Time
CallStack []runtime.Frame
}
type Content struct {
@ -32,6 +34,8 @@ type Content struct {
Value any `json:"value"`
}
type Map map[string]any
func NewEntry(logger *Logger) *Entry {
return &Entry{
Logger: logger,
@ -47,6 +51,13 @@ func (entry *Entry) With(key string, value any) *Entry {
return entry
}
func (entry *Entry) WithMap(entries Map) *Entry {
for key, value := range entries {
entry.With(key, value)
}
return entry
}
func (entry *Entry) WithContent(content []Content) *Entry {
entry.Content = append(entry.Content, content...)
return entry

View file

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package golog
package log
type Formatter interface {
Begin(*Entry)

View file

@ -12,10 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package golog
package log
import (
"encoding/json"
"fmt"
"log"
"time"
)
@ -23,7 +24,7 @@ import (
type FormatterJSON struct {
FlatContent bool
TimeFormat string
data map[string]interface{}
data map[string]any
}
func NewFormatterJSON() *FormatterJSON {
@ -34,7 +35,7 @@ func NewFormatterJSON() *FormatterJSON {
func (formatter *FormatterJSON) Begin(entry *Entry) {
// reset formatter
formatter.data = make(map[string]interface{})
formatter.data = make(map[string]any)
// add timestamp
formatter.data["time"] = entry.Time.Format(formatter.TimeFormat)
@ -47,6 +48,13 @@ func (formatter *FormatterJSON) Process(entry *Entry) {
// add message
formatter.data["message"] = entry.Message
// add call stack
if len(entry.CallStack) > 0 {
caller := entry.CallStack[0]
formatter.data["file"] = fmt.Sprintf("%s:%d", caller.File, caller.Line)
formatter.data["function"] = caller.Function
}
// add additional fields
if formatter.FlatContent {
for _, content := range entry.Content {
@ -57,7 +65,7 @@ func (formatter *FormatterJSON) Process(entry *Entry) {
}
}
func (formatter *FormatterJSON) End(entry *Entry) []byte {
func (formatter *FormatterJSON) End(_ *Entry) []byte {
// build JSON
data, err := json.Marshal(formatter.data)
if err != nil {

View file

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package golog
package log
import "testing"

View file

@ -12,16 +12,21 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package golog
package log
import (
"fmt"
"slices"
"strings"
"time"
)
type FormatterKeyValue struct {
TimeFormat string
TimeFormat string
// HighPriorityKeys are printed before the actual log message
HighPriorityKeys []string
// PriorityKeys are printed after the log message
PriorityKeys []string
builder strings.Builder
isFirstKVWritten bool
}
@ -35,6 +40,7 @@ func NewFormatterKeyValue() *FormatterKeyValue {
func (formatter *FormatterKeyValue) Begin(entry *Entry) {
// reset builder
formatter.builder.Reset()
formatter.isFirstKVWritten = false
// add timestamp
formatter.addKV("time", entry.Time.Format(formatter.TimeFormat))
@ -44,12 +50,36 @@ func (formatter *FormatterKeyValue) Begin(entry *Entry) {
}
func (formatter *FormatterKeyValue) Process(entry *Entry) {
// log high priority keys
for _, content := range entry.Content {
if slices.Contains(formatter.HighPriorityKeys, content.Key) {
formatter.addKV(content.Key, content.Value)
}
}
// add message
formatter.addKV("message", entry.Message)
// log priority keys
for _, content := range entry.Content {
if slices.Contains(formatter.PriorityKeys, content.Key) {
formatter.addKV(content.Key, content.Value)
}
}
// add call stack
if len(entry.CallStack) > 0 {
caller := entry.CallStack[0]
formatter.addKV("file", fmt.Sprintf("%s:%d", caller.File, caller.Line))
formatter.addKV("function", caller.Function)
}
// add additional fields
for _, content := range entry.Content {
formatter.addKV(content.Key, content.Value)
if !slices.Contains(formatter.HighPriorityKeys, content.Key) &&
!slices.Contains(formatter.PriorityKeys, content.Key) {
formatter.addKV(content.Key, content.Value)
}
}
}
@ -58,7 +88,7 @@ func (formatter *FormatterKeyValue) addKV(key string, value any) {
val := fmt.Sprintf("%v", value)
// check, if value should be escaped
if strings.ContainsAny(val, " \t\n\r=") {
if strings.ContainsAny(val, " \t\n\r=:\\") {
val = fmt.Sprintf("%q", val)
}
@ -74,7 +104,7 @@ func (formatter *FormatterKeyValue) addKV(key string, value any) {
formatter.builder.WriteString(val)
}
func (formatter *FormatterKeyValue) End(entry *Entry) []byte {
func (formatter *FormatterKeyValue) End(_ *Entry) []byte {
// send data
return []byte(formatter.builder.String())
}

View file

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package golog
package log
import "testing"
@ -26,7 +26,7 @@ func TestFormatterKeyValue(t *testing.T) {
},
Level: LevelInfo,
}
result := `time=0001-01-01T00:00:00Z level=INFO message="Some Text" k1=v1 k2=v2`
result := `time="0001-01-01T00:00:00Z" level=INFO message="Some Text" k1=v1 k2=v2`
// execute formatter
formatter := NewFormatterKeyValue()

2
go.mod
View file

@ -1,3 +1,3 @@
module gitlab.com/martinr92/golog
module git.martin-riedl.de/golang/log
go 1.20

View file

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package golog
package log
type Level uint32

View file

@ -12,12 +12,18 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package golog
package log
import "time"
import (
"runtime"
"strings"
"time"
)
var Default = NewLoggerDefault()
const maxCallersPCs = 20
type Logger struct {
Outputs []*Output
}
@ -36,12 +42,66 @@ func (logger *Logger) LogEntry(entry *Entry) {
// set execution time
entry.Time = time.Now()
// set call stack
entry.CallStack = logger.callStack()
// run each output
for _, output := range logger.Outputs {
output.Send(entry)
}
}
func (logger *Logger) callStack() (response []runtime.Frame) {
// get call stack
pcs := make([]uintptr, maxCallersPCs)
runtime.Callers(1, pcs)
frames := runtime.CallersFrames(pcs)
var self string
for {
// check for next frame
frame, more := frames.Next()
if !more {
break
}
// extract self package
if self == "" {
self = logger.framePackage(frame)
continue
}
// ignore self
currentPackage := logger.framePackage(frame)
if currentPackage != self {
response = append(response, frame)
}
}
return
}
func (logger *Logger) framePackage(frame runtime.Frame) string {
s := frame.Function
// special handling, if no full qualified package exists
lastSlash := strings.LastIndexByte(s, '/')
if lastSlash == -1 {
firstDot := strings.IndexByte(s, '.')
if firstDot == -1 {
return s
}
return s[:firstDot]
}
// extract package
firstDot := strings.IndexByte(s[:lastSlash], '.')
if firstDot == -1 {
return s[:lastSlash]
}
return s[:lastSlash+firstDot]
}
func (logger *Logger) Log(level Level, args ...any) {
entry := NewEntry(logger)
entry.Log(level, args...)
@ -53,6 +113,12 @@ func (logger *Logger) With(key string, value any) *Entry {
return entry
}
func (logger *Logger) WithMap(entries Map) *Entry {
entry := NewEntry(logger)
entry.WithMap(entries)
return entry
}
func (logger *Logger) WithContent(content []Content) *Entry {
entry := NewEntry(logger)
entry.WithContent(content)

View file

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package golog
package log
import "sync"

View file

@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package golog
package log
type Printer interface {
Write(p []byte) (n int, err error)
Write(p []byte)
}

View file

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package golog
package log
import "os"
@ -26,6 +26,7 @@ func NewPrinterStdout() *PrinterStdout {
}
}
func (printer *PrinterStdout) Write(p []byte) (n int, err error) {
return printer.out.Write(p)
func (printer *PrinterStdout) Write(p []byte) {
out := append(p, '\n')
printer.out.Write(out)
}

9
renovate.json Normal file
View file

@ -0,0 +1,9 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"local>ci/renovate//configs/base"
],
"baseBranches": [
"develop"
]
}