Compare commits
22 commits
Author | SHA1 | Date | |
---|---|---|---|
1d7177fb26 | |||
f44e60cca4 | |||
5d174f8685 | |||
a2883c1077 | |||
ba2ba315d0 | |||
a1c991bcbd | |||
eb8b9abe1a | |||
56b1e3612b | |||
3e9453bf56 | |||
ea862ef834 | |||
794f3ed498 | |||
12214121c2 | |||
0819e83691 | |||
b44771fddb | |||
2a3fcd390a | |||
4286b9e5ce | |||
ff82c54533 | |||
a98708a4a9 | |||
6353cb045a | |||
d91c971435 | |||
9670c407b6 | |||
e20fe3c887 |
20 changed files with 455 additions and 43 deletions
81
.forgejo/workflows/build.yml
Normal file
81
.forgejo/workflows/build.yml
Normal 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 .
|
20
.forgejo/workflows/release.yml
Normal file
20
.forgejo/workflows/release.yml
Normal 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
1
.gitignore
vendored
|
@ -1,2 +1,3 @@
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
.idea
|
||||||
.vscode
|
.vscode
|
72
.releaserc
Normal file
72
.releaserc
Normal 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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
52
README.md
52
README.md
|
@ -1,4 +1,56 @@
|
||||||
|
# log
|
||||||
|
|
||||||
|
Log is a simple logging framework for golang.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
```golang
|
||||||
|
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 "git.martin-riedl.de/golang/log"
|
||||||
|
|
||||||
|
var myLog := log.NewLoggerDefault()
|
||||||
|
myLog.Info("My First Info Message")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Outputs / Formatter / Printer
|
||||||
|
|
||||||
|
- `Formatter` defines the output format (e.g. Key/Value or JSON)
|
||||||
|
- `Printer` defines the message output location (e.g. stdout or file)
|
||||||
|
- `Output` is a combination of `Formatter` and `Printer`
|
||||||
|
|
||||||
|
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 "git.martin-riedl.de/golang/log"
|
||||||
|
|
||||||
|
var output1 := NewOutput(log.LevelInfo, NewFormatterKeyValue(), NewPrinterStdout()
|
||||||
|
var output2 := NewOutput(log.LevelError, NewFormatterJSON(), NewPrinterStdout())
|
||||||
|
|
||||||
|
var myLog := log.NewLogger(
|
||||||
|
[]*Output{output1, output2}
|
||||||
|
)
|
||||||
|
myLog.Info("My First Info Message")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Custom Formatter / Printer
|
||||||
|
|
||||||
|
If you need a special `Formatter` or `Printer`: simply implement the Interface (same name) and assign it to an `Output`.
|
||||||
|
|
||||||
# License
|
# License
|
||||||
|
|
||||||
```
|
```
|
||||||
Copyright 2023 Martin Riedl
|
Copyright 2023 Martin Riedl
|
||||||
|
|
||||||
|
|
30
cmd/keyValueFormatter/main.go
Normal file
30
cmd/keyValueFormatter/main.go
Normal 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
24
cmd/simple/main.go
Normal 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")
|
||||||
|
}
|
13
entry.go
13
entry.go
|
@ -12,10 +12,11 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package golog
|
package log
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"runtime"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -25,6 +26,7 @@ type Entry struct {
|
||||||
Message string
|
Message string
|
||||||
Level Level
|
Level Level
|
||||||
Time time.Time
|
Time time.Time
|
||||||
|
CallStack []runtime.Frame
|
||||||
}
|
}
|
||||||
|
|
||||||
type Content struct {
|
type Content struct {
|
||||||
|
@ -32,6 +34,8 @@ type Content struct {
|
||||||
Value any `json:"value"`
|
Value any `json:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Map map[string]any
|
||||||
|
|
||||||
func NewEntry(logger *Logger) *Entry {
|
func NewEntry(logger *Logger) *Entry {
|
||||||
return &Entry{
|
return &Entry{
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
|
@ -47,6 +51,13 @@ func (entry *Entry) With(key string, value any) *Entry {
|
||||||
return 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 {
|
func (entry *Entry) WithContent(content []Content) *Entry {
|
||||||
entry.Content = append(entry.Content, content...)
|
entry.Content = append(entry.Content, content...)
|
||||||
return entry
|
return entry
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package golog
|
package log
|
||||||
|
|
||||||
type Formatter interface {
|
type Formatter interface {
|
||||||
Begin(*Entry)
|
Begin(*Entry)
|
||||||
|
|
|
@ -12,10 +12,11 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package golog
|
package log
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -23,7 +24,7 @@ import (
|
||||||
type FormatterJSON struct {
|
type FormatterJSON struct {
|
||||||
FlatContent bool
|
FlatContent bool
|
||||||
TimeFormat string
|
TimeFormat string
|
||||||
data map[string]interface{}
|
data map[string]any
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFormatterJSON() *FormatterJSON {
|
func NewFormatterJSON() *FormatterJSON {
|
||||||
|
@ -34,7 +35,7 @@ func NewFormatterJSON() *FormatterJSON {
|
||||||
|
|
||||||
func (formatter *FormatterJSON) Begin(entry *Entry) {
|
func (formatter *FormatterJSON) Begin(entry *Entry) {
|
||||||
// reset formatter
|
// reset formatter
|
||||||
formatter.data = make(map[string]interface{})
|
formatter.data = make(map[string]any)
|
||||||
|
|
||||||
// add timestamp
|
// add timestamp
|
||||||
formatter.data["time"] = entry.Time.Format(formatter.TimeFormat)
|
formatter.data["time"] = entry.Time.Format(formatter.TimeFormat)
|
||||||
|
@ -47,6 +48,13 @@ func (formatter *FormatterJSON) Process(entry *Entry) {
|
||||||
// add message
|
// add message
|
||||||
formatter.data["message"] = entry.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
|
// add additional fields
|
||||||
if formatter.FlatContent {
|
if formatter.FlatContent {
|
||||||
for _, content := range entry.Content {
|
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
|
// build JSON
|
||||||
data, err := json.Marshal(formatter.data)
|
data, err := json.Marshal(formatter.data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package golog
|
package log
|
||||||
|
|
||||||
import "testing"
|
import "testing"
|
||||||
|
|
||||||
|
|
|
@ -12,16 +12,21 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package golog
|
package log
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type FormatterKeyValue struct {
|
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
|
builder strings.Builder
|
||||||
isFirstKVWritten bool
|
isFirstKVWritten bool
|
||||||
}
|
}
|
||||||
|
@ -35,6 +40,7 @@ func NewFormatterKeyValue() *FormatterKeyValue {
|
||||||
func (formatter *FormatterKeyValue) Begin(entry *Entry) {
|
func (formatter *FormatterKeyValue) Begin(entry *Entry) {
|
||||||
// reset builder
|
// reset builder
|
||||||
formatter.builder.Reset()
|
formatter.builder.Reset()
|
||||||
|
formatter.isFirstKVWritten = false
|
||||||
|
|
||||||
// add timestamp
|
// add timestamp
|
||||||
formatter.addKV("time", entry.Time.Format(formatter.TimeFormat))
|
formatter.addKV("time", entry.Time.Format(formatter.TimeFormat))
|
||||||
|
@ -44,21 +50,45 @@ func (formatter *FormatterKeyValue) Begin(entry *Entry) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (formatter *FormatterKeyValue) Process(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
|
// add message
|
||||||
formatter.addKV("message", entry.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
|
// add additional fields
|
||||||
for _, content := range entry.Content {
|
for _, content := range entry.Content {
|
||||||
|
if !slices.Contains(formatter.HighPriorityKeys, content.Key) &&
|
||||||
|
!slices.Contains(formatter.PriorityKeys, content.Key) {
|
||||||
formatter.addKV(content.Key, content.Value)
|
formatter.addKV(content.Key, content.Value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (formatter *FormatterKeyValue) addKV(key string, value any) {
|
func (formatter *FormatterKeyValue) addKV(key string, value any) {
|
||||||
// convert any to string
|
// convert any to string
|
||||||
val := fmt.Sprintf("%v", value)
|
val := fmt.Sprintf("%v", value)
|
||||||
|
|
||||||
// check, if value should be escaped
|
// 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)
|
val = fmt.Sprintf("%q", val)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,7 +104,7 @@ func (formatter *FormatterKeyValue) addKV(key string, value any) {
|
||||||
formatter.builder.WriteString(val)
|
formatter.builder.WriteString(val)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (formatter *FormatterKeyValue) End(entry *Entry) []byte {
|
func (formatter *FormatterKeyValue) End(_ *Entry) []byte {
|
||||||
// send data
|
// send data
|
||||||
return []byte(formatter.builder.String())
|
return []byte(formatter.builder.String())
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package golog
|
package log
|
||||||
|
|
||||||
import "testing"
|
import "testing"
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ func TestFormatterKeyValue(t *testing.T) {
|
||||||
},
|
},
|
||||||
Level: LevelInfo,
|
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
|
// execute formatter
|
||||||
formatter := NewFormatterKeyValue()
|
formatter := NewFormatterKeyValue()
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -1,3 +1,3 @@
|
||||||
module gitlab.com/martinr92/golog
|
module git.martin-riedl.de/golang/log
|
||||||
|
|
||||||
go 1.20
|
go 1.20
|
||||||
|
|
2
level.go
2
level.go
|
@ -12,7 +12,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package golog
|
package log
|
||||||
|
|
||||||
type Level uint32
|
type Level uint32
|
||||||
|
|
||||||
|
|
81
logger.go
81
logger.go
|
@ -12,43 +12,96 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package golog
|
package log
|
||||||
|
|
||||||
import "time"
|
import (
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
var Default = NewLoggerDefault()
|
var Default = NewLoggerDefault()
|
||||||
|
|
||||||
|
const maxCallersPCs = 20
|
||||||
|
|
||||||
type Logger struct {
|
type Logger struct {
|
||||||
Level Level
|
|
||||||
Outputs []*Output
|
Outputs []*Output
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLogger(level Level, outputs []*Output) *Logger {
|
func NewLogger(outputs []*Output) *Logger {
|
||||||
return &Logger{
|
return &Logger{
|
||||||
Level: LevelInfo,
|
|
||||||
Outputs: outputs,
|
Outputs: outputs,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLoggerDefault() *Logger {
|
func NewLoggerDefault() *Logger {
|
||||||
return NewLogger(LevelInfo, []*Output{NewOutputDefault()})
|
return NewLogger([]*Output{NewOutputDefault()})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) LogEntry(entry *Entry) {
|
func (logger *Logger) LogEntry(entry *Entry) {
|
||||||
// check, if message should be printed
|
|
||||||
if entry.Level > logger.Level {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// set execution time
|
// set execution time
|
||||||
entry.Time = time.Now()
|
entry.Time = time.Now()
|
||||||
|
|
||||||
|
// set call stack
|
||||||
|
entry.CallStack = logger.callStack()
|
||||||
|
|
||||||
// run each output
|
// run each output
|
||||||
for _, output := range logger.Outputs {
|
for _, output := range logger.Outputs {
|
||||||
output.Send(entry)
|
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) {
|
func (logger *Logger) Log(level Level, args ...any) {
|
||||||
entry := NewEntry(logger)
|
entry := NewEntry(logger)
|
||||||
entry.Log(level, args...)
|
entry.Log(level, args...)
|
||||||
|
@ -60,6 +113,12 @@ func (logger *Logger) With(key string, value any) *Entry {
|
||||||
return 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 {
|
func (logger *Logger) WithContent(content []Content) *Entry {
|
||||||
entry := NewEntry(logger)
|
entry := NewEntry(logger)
|
||||||
entry.WithContent(content)
|
entry.WithContent(content)
|
||||||
|
|
26
output.go
26
output.go
|
@ -12,32 +12,43 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package golog
|
package log
|
||||||
|
|
||||||
import "sync"
|
import "sync"
|
||||||
|
|
||||||
type Output struct {
|
type Output struct {
|
||||||
|
Level Level
|
||||||
Formatter Formatter
|
Formatter Formatter
|
||||||
formatterLock sync.Mutex
|
formatterLock sync.Mutex
|
||||||
Printer Printer
|
Printer Printer
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewOutput(formatter Formatter, printer Printer) *Output {
|
func NewOutput(level Level, formatter Formatter, printer Printer) *Output {
|
||||||
return &Output{
|
return &Output{
|
||||||
|
Level: level,
|
||||||
Formatter: formatter,
|
Formatter: formatter,
|
||||||
Printer: printer,
|
Printer: printer,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewOutputDefault() *Output {
|
func NewOutputDefault() *Output {
|
||||||
return NewOutput(NewFormatterKeyValue(), NewPrinterStdout())
|
return NewOutput(LevelInfo, NewFormatterKeyValue(), NewPrinterStdout())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (output *Output) Send(entry *Entry) {
|
func (output *Output) Send(entry *Entry) {
|
||||||
output.format(entry)
|
// check, if message should be printed
|
||||||
|
if entry.Level > output.Level {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (output *Output) format(entry *Entry) {
|
// execute formatter
|
||||||
|
data := output.format(entry)
|
||||||
|
|
||||||
|
// execute printer
|
||||||
|
output.Printer.Write(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (output *Output) format(entry *Entry) []byte {
|
||||||
output.formatterLock.Lock()
|
output.formatterLock.Lock()
|
||||||
defer output.formatterLock.Unlock()
|
defer output.formatterLock.Unlock()
|
||||||
|
|
||||||
|
@ -48,5 +59,8 @@ func (output *Output) format(entry *Entry) {
|
||||||
output.Formatter.Process(entry)
|
output.Formatter.Process(entry)
|
||||||
|
|
||||||
// finalize formatter
|
// finalize formatter
|
||||||
output.Formatter.End(entry)
|
data := output.Formatter.End(entry)
|
||||||
|
|
||||||
|
// return generated data
|
||||||
|
return data
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,8 +12,8 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package golog
|
package log
|
||||||
|
|
||||||
type Printer interface {
|
type Printer interface {
|
||||||
Write(p []byte) (n int, err error)
|
Write(p []byte)
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package golog
|
package log
|
||||||
|
|
||||||
import "os"
|
import "os"
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@ func NewPrinterStdout() *PrinterStdout {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (printer *PrinterStdout) Write(p []byte) (n int, err error) {
|
func (printer *PrinterStdout) Write(p []byte) {
|
||||||
return printer.out.Write(p)
|
out := append(p, '\n')
|
||||||
|
printer.out.Write(out)
|
||||||
}
|
}
|
||||||
|
|
9
renovate.json
Normal file
9
renovate.json
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
|
"extends": [
|
||||||
|
"local>ci/renovate//configs/base"
|
||||||
|
],
|
||||||
|
"baseBranches": [
|
||||||
|
"develop"
|
||||||
|
]
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue