Skip to content

Commit

Permalink
feat: init
Browse files Browse the repository at this point in the history
  • Loading branch information
mr-karan committed Jun 26, 2022
1 parent 5410e05 commit 536c7b9
Show file tree
Hide file tree
Showing 13 changed files with 834 additions and 0 deletions.
40 changes: 40 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: build

on:
push:
branches:
- 'main'
tags:
- 'v*'
pull_request:

jobs:
build:
strategy:
matrix:
go-version: [~1.18]
os: [ ubuntu-latest, macos-latest, windows-latest ]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: actions/setup-go@v3
with:
go-version: ${{ matrix.go-version }}
- uses: actions/cache@v3
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- run: go test -v -failfast -race -coverpkg=./... -covermode=atomic -coverprofile=coverage.txt

- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
with:
version: latest
args: release --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
21 changes: 21 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: golangci-lint
on:
push:
tags:
- v*
branches:
- main
pull_request:
jobs:
golangci:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v3
with:
go-version: ~1.16
- uses: actions/checkout@v3
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
with:
skip-go-installation: true
23 changes: 23 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib

# Test binary, built with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# Dependency directories (remove the comment below to include it)
# vendor/

config.toml
.env
bin/
data/
dist/
.vscode/
coverage.txt
8 changes: 8 additions & 0 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
before:
hooks:
- go mod tidy
builds:
- skip: true

snapshot:
name_template: '{{ incpatch .Version }}-next'
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.PHONY: test
test:
go test -v -failfast -race -coverpkg=./... -covermode=atomic -coverprofile=coverage.txt

benchmark:
go test -bench=. -benchmem
104 changes: 104 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# logf

[![Go Reference](https://pkg.go.dev/badge/github.com/mr-karan/logf.svg)](https://pkg.go.dev/github.com/mr-karan/logf)
[![Go Report Card](https://goreportcard.com/badge/mr-karan/logf)](https://goreportcard.com/report/mr-karan/logf)
[![GitHub Actions](https://github.com/mr-karan/logf/actions/workflows/build.yml/badge.svg)](https://github.com/mr-karan/logf/actions/workflows/build.yml)


logf provides a minimal logging interface for Go applications. It emits **structured logs** ([`logfmt`](https://brandur.org/logfmt) style) in human readable and machine friendly way.

## Example

```go
package main

import (
"errors"
"time"

"github.com/mr-karan/logf"
)

func main() {
logger := logf.New()
// Basic log.
logger.Info("starting app")

// Change verbosity on the fly.
logger.SetLevel(logf.DebugLevel)
logger.Debug("meant for debugging app")

// Add extra keys to the log.
logger.WithFields(logf.Fields{
"component": "api",
"user": "karan",
}).Info("logging with some extra metadata")

// Log with error key.
logger.WithError(errors.New("this is a dummy error")).Error("error fetching details")

// Enable `caller` field in the log and specify the number of frames to skip to get the caller.
logger.SetCallerFrame(true, 3)
// Change the default timestamp format.
logger.SetTimestampFormat(time.RFC3339Nano)

// Create a logger and add fields which will be logged in every line.
requestLogger := logger.WithFields(logf.Fields{"request_id": "3MG91VKP", "ip": "1.1.1.1", "method": "GET"})
requestLogger.Info("request success")
requestLogger.Warn("this isn't supposed to happen")

// Log the error and set exit code as 1.
logger.Fatal("goodbye world")
}
```

### Text Output

```bash
timestamp=2022-06-26T11:56:46+05:30 level=info message=starting app caller=/home/karan/Code/Personal/logf/examples/main.go:13
timestamp=2022-06-26T11:56:46+05:30 level=debug message=meant for debugging app caller=/home/karan/Code/Personal/logf/examples/main.go:17 level=debug message=meant for debugging app timestamp=2022-06-26T11:56:46+05:30 caller=/home/karan/Code/Personal/logf/examples/main.go:17
timestamp=2022-06-26T11:56:46+05:30 level=info message=logging with some extra metadata component=api user=karan caller=/home/karan/Code/Personal/logf/examples/main.go:23
timestamp=2022-06-26T11:56:46+05:30 level=error message=error fetching details error=this is a dummy error caller=/home/karan/Code/Personal/logf/examples/main.go:26
timestamp=2022-06-26T11:56:46.412189111+05:30 level=info message=request success ip=1.1.1.1 method=GET request_id=3MG91VKP
timestamp=2022-06-26T11:56:46.412204619+05:30 level=warn message=this isn't supposed to happen ip=1.1.1.1 level=warn message=this isn't supposed to happen method=GET request_id=3MG91VKP timestamp=2022-06-26T11:56:46.412204619+05:30
timestamp=2022-06-26T11:56:46.412218628+05:30 level=fatal message=goodbye world ip=1.1.1.1 level=fatal message=goodbye world method=GET request_id=3MG91VKP timestamp=2022-06-26T11:56:46.412218628+05:30
exit status 1
```

### Console Output

![](examples/screenshot.png)

## Why another lib

Agreed there are many logging libraries out there but I was dissatisfied with the current options.

`logf` satisfies my constraints of:

- Clean API
- Minimal Dependencies
- Structured logging but human readable (`logfmt`!)
- Sane defaults out of the box

## Benchmarks

You can run benchmarks with `make bench`.

```
BenchmarkNoField-8 290559 3797 ns/op 1576 B/op 74 allocs/op
BenchmarkNoField_NoColor-8 1313766 924.8 ns/op 328 B/op 11 allocs/op
BenchmarkOneField-8 219285 5445 ns/op 2609 B/op 103 allocs/op
BenchmarkOneField_NoColor-8 668251 1550 ns/op 928 B/op 19 allocs/op
BenchmarkThreeFields-8 152988 7992 ns/op 3953 B/op 153 allocs/op
BenchmarkThreeFields_NoColor-8 516135 2220 ns/op 1320 B/op 27 allocs/op
BenchmarkHugePayload-8 57367 22658 ns/op 15121 B/op 356 allocs/op
BenchmarkHugePayload_NoColor-8 140937 7404 ns/op 8342 B/op 62 allocs/op
BenchmarkErrorField-8 212184 5639 ns/op 2657 B/op 104 allocs/op
BenchmarkErrorField_NoColor-8 703165 1593 ns/op 952 B/op 20 allocs/op
```

For a comparison with existing popular libs, visit [uber-go/zap#performance](https://github.com/uber-go/zap#performance).

## LICENSE

[LICENSE](./LICENSE)
166 changes: 166 additions & 0 deletions benchmark_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package logf_test

import (
"errors"
"io"
"testing"

"github.com/mr-karan/logf"
)

func BenchmarkNoField(b *testing.B) {
logger := logf.New()
logger.SetWriter(io.Discard)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
logger.Info("hello world")
}
}

func BenchmarkNoField_NoColor(b *testing.B) {
logger := logf.New()
logger.SetWriter(io.Discard)
logger.SetColorOutput(false)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
logger.Info("hello world")
}
}

func BenchmarkOneField(b *testing.B) {
logger := logf.New()
logger.SetWriter(io.Discard)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
logger.WithFields(logf.Fields{"stack": "testing"}).Info("hello world")
}
}

func BenchmarkOneField_NoColor(b *testing.B) {
logger := logf.New()
logger.SetWriter(io.Discard)
logger.SetColorOutput(false)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
logger.WithFields(logf.Fields{"stack": "testing"}).Info("hello world")
}
}

func BenchmarkThreeFields(b *testing.B) {
logger := logf.New()
logger.SetWriter(io.Discard)
b.ReportAllocs()
b.ResetTimer()

for i := 0; i < b.N; i++ {
logger.WithFields(logf.Fields{
"component": "api",
"method": "GET",
"bytes": 1 << 18,
}).Info("request completed")
}
}

func BenchmarkThreeFields_NoColor(b *testing.B) {
logger := logf.New()
logger.SetWriter(io.Discard)
logger.SetColorOutput(false)
b.ReportAllocs()
b.ResetTimer()

for i := 0; i < b.N; i++ {
logger.WithFields(logf.Fields{
"component": "api",
"method": "GET",
"bytes": 1 << 18,
}).Info("request completed")
}
}

func BenchmarkHugePayload(b *testing.B) {
logger := logf.New()
logger.SetWriter(io.Discard)
b.ReportAllocs()
b.ResetTimer()

for i := 0; i < b.N; i++ {
logger.WithFields(logf.Fields{
"id": 11,
"title": "perfume Oil",
"description": "Mega Discount, Impression of A...",
"price": 13,
"discountPercentage": 8.4,
"rating": 4.26,
"stock": 65,
"brand": "Impression of Acqua Di Gio",
"category": "fragrances",
"thumbnail": "https://dummyjson.com/image/i/products/11/thumbnail.jpg",
"images": []string{
"https://dummyjson.com/image/i/products/11/1.jpg",
"https://dummyjson.com/image/i/products/11/2.jpg",
"https://dummyjson.com/image/i/products/11/3.jpg",
"https://dummyjson.com/image/i/products/11/thumbnail.jpg",
},
}).Info("fetched details")
}
}

func BenchmarkHugePayload_NoColor(b *testing.B) {
logger := logf.New()
logger.SetWriter(io.Discard)
logger.SetColorOutput(false)
b.ReportAllocs()
b.ResetTimer()

for i := 0; i < b.N; i++ {
logger.WithFields(logf.Fields{
"id": 11,
"title": "perfume Oil",
"description": "Mega Discount, Impression of A...",
"price": 13,
"discountPercentage": 8.4,
"rating": 4.26,
"stock": 65,
"brand": "Impression of Acqua Di Gio",
"category": "fragrances",
"thumbnail": "https://dummyjson.com/image/i/products/11/thumbnail.jpg",
"images": []string{
"https://dummyjson.com/image/i/products/11/1.jpg",
"https://dummyjson.com/image/i/products/11/2.jpg",
"https://dummyjson.com/image/i/products/11/3.jpg",
"https://dummyjson.com/image/i/products/11/thumbnail.jpg",
},
}).Info("fetched details")
}
}

func BenchmarkErrorField(b *testing.B) {
logger := logf.New()
logger.SetWriter(io.Discard)
b.ReportAllocs()
b.ResetTimer()

fakeErr := errors.New("fake error")

for i := 0; i < b.N; i++ {
logger.WithError(fakeErr).Error("request failed")
}
}

func BenchmarkErrorField_NoColor(b *testing.B) {
logger := logf.New()
logger.SetWriter(io.Discard)
logger.SetColorOutput(false)
b.ReportAllocs()
b.ResetTimer()

fakeErr := errors.New("fake error")

for i := 0; i < b.N; i++ {
logger.WithError(fakeErr).Error("request failed")
}
}
Loading

0 comments on commit 536c7b9

Please sign in to comment.