From 3f59532c1aadc8cec044d1a9166add6ae5f1cf0d Mon Sep 17 00:00:00 2001 From: bxlxx Date: Wed, 16 Mar 2022 16:12:06 +0800 Subject: [PATCH] initialize spoa connector --- .github/workflows/lint.yaml | 32 +++++++ .gitignore | 11 ++- .pre-commit-config.yaml | 15 +++ CODE_OF_CONDUCT.md | 128 +++++++++++++++++++++++++ Makefile | 13 +++ cmd/main.go | 34 +++++++ config.yaml.default | 5 + config/config.go | 99 ++++++++++++++++++++ doc/config/coraza.cfg | 18 ++++ doc/config/haproxy.cfg | 28 ++++++ go.mod | 15 +++ go.sum | 64 +++++++++++++ pkg/logger/field.go | 180 ++++++++++++++++++++++++++++++++++++ pkg/logger/logger.go | 124 +++++++++++++++++++++++++ pkg/logger/method.go | 65 +++++++++++++ pkg/logger/tee.go | 70 ++++++++++++++ 16 files changed, 897 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/lint.yaml create mode 100644 .pre-commit-config.yaml create mode 100644 CODE_OF_CONDUCT.md create mode 100644 Makefile create mode 100644 cmd/main.go create mode 100644 config.yaml.default create mode 100644 config/config.go create mode 100644 doc/config/coraza.cfg create mode 100644 doc/config/haproxy.cfg create mode 100644 go.mod create mode 100644 go.sum create mode 100644 pkg/logger/field.go create mode 100644 pkg/logger/logger.go create mode 100644 pkg/logger/method.go create mode 100644 pkg/logger/tee.go diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml new file mode 100644 index 0000000..91f9aaf --- /dev/null +++ b/.github/workflows/lint.yaml @@ -0,0 +1,32 @@ +name: lint (pre-commit) + +on: + pull_request: + branches: + - dev + push: + branches: + - dev + +jobs: + pre-commit: + runs-on: ubuntu-latest + steps: + - name: Install Go + uses: actions/setup-go@v2 + with: + go-version: v1.17.x + + - name: Install dependencies + run: | + cd /tmp && go install github.com/go-critic/go-critic/cmd/gocritic@latest + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.43.0 + go install golang.org/x/lint/golint@latest + go install github.com/fzipp/gocyclo/cmd/gocyclo@latest + + - uses: actions/checkout@v3 + - uses: actions/setup-python@v2 + - uses: pre-commit/action@v2.0.3 + with: + extra_args: --all-files +` \ No newline at end of file diff --git a/.gitignore b/.gitignore index 66fd13c..eda57e5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,18 @@ -# Binaries for programs and plugins *.exe *.exe~ *.dll *.so +*.o *.dylib - +.DS_Store +.idea # Test binary, built with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out +vendor/ -# Dependency directories (remove the comment below to include it) -# vendor/ +# local files +config.yaml +logs/ \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..c721513 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,15 @@ +repos: + - repo: git://github.com/dnephin/pre-commit-golang + rev: v0.4.0 + hooks: + - id: go-fmt + # has some question with go vet + # - id: go-vet + - id: go-lint + - id: go-imports + - id: go-cyclo + args: [-over=15] + - id: golangci-lint + - id: go-critic + - id: go-unit-tests + - id: go-mod-tidy \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..a412ce8 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +bxlxx.wu@outlook.com. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3237ed4 --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ +# Copyright 2022 The Corazawaf Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. \ No newline at end of file diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..af87830 --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,34 @@ +// Copyright 2022 The Corazawaf Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "flag" + "github.com/corazawaf/coraza-spoa/config" + "github.com/corazawaf/coraza-spoa/pkg/logger" +) + +func main() { + defer func() { + if err := logger.Sync(); err != nil { + _ = err + } + }() + + flag.Parse() + if err := config.InitConfig(); err != nil { + panic(err) + } +} diff --git a/config.yaml.default b/config.yaml.default new file mode 100644 index 0000000..ecdd984 --- /dev/null +++ b/config.yaml.default @@ -0,0 +1,5 @@ +log: + # The Log level configuration, one of: debug/info/warn/error/panic/fatal + level: debug + # The Log file dir of the coraza-server + dir: /var/logs/coraza-server \ No newline at end of file diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..a531eed --- /dev/null +++ b/config/config.go @@ -0,0 +1,99 @@ +// Copyright 2022 The Corazawaf Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package config + +import ( + "flag" + "fmt" + "github.com/corazawaf/coraza-spoa/pkg/logger" + "gopkg.in/yaml.v3" + "os" +) + +// C is used to store the configuration. +var C Config + +func init() { + flag.StringVar(&C.ConfigFile, "config-file", "./config.yml", "The configuration file of the coraza-spoa. (default: ./config.yml)") +} + +// Config is used to configure coraza-server. +type Config struct { + Log Log `yaml:"log"` + + // ConfigFile is the configuration file of the coraza-server. + ConfigFile string +} + +// Log is used to configure the level and dir of the log. +type Log struct { + Level string `yaml:"level"` + Dir string `yaml:"dir"` +} + +// InitConfig initializes the configuration. +func InitConfig() error { + f, err := os.Open(C.ConfigFile) + if err != nil { + return err + } + defer f.Close() + + err = yaml.NewDecoder(f).Decode(&C) + if err != nil { + return err + } + + // set the log configuration + initLog() + + return nil +} + +func initLog() { + var tops = []logger.TeeOption{ + { + Filename: fmt.Sprintf("%s/server.log", C.Log.Dir), + ROpts: logger.RotateOptions{ + MaxSize: 128, + MaxAge: 7, + MaxBackups: 30, + Compress: true, + }, + Lef: func(level logger.Level) bool { + l, err := logger.ParseLevel(C.Log.Level) + if err != nil { + panic(err) + } + return level >= l && level < logger.ErrorLevel + }, + }, + { + Filename: fmt.Sprintf("%s/error.log", C.Log.Dir), + ROpts: logger.RotateOptions{ + MaxSize: 128, + MaxAge: 7, + MaxBackups: 30, + Compress: true, + }, + Lef: func(level logger.Level) bool { + return level >= logger.ErrorLevel + }, + }, + } + + // reset default logger for using global logger + logger.NewTeeWithRotate(tops).Reset() +} diff --git a/doc/config/coraza.cfg b/doc/config/coraza.cfg new file mode 100644 index 0000000..6d72326 --- /dev/null +++ b/doc/config/coraza.cfg @@ -0,0 +1,18 @@ +# https://github.com/haproxy/haproxy/blob/master/doc/SPOE.txt +[coraza] +spoe-agent coraza-agent + messages coraza-req coraza-res + option var-prefix coraza + timeout hello 100ms + timeout idle 2m + timeout processing 15ms + use-backend coraza-servers + log global + +spoe-message coraza-req + args unique-id src method path query req.ver req.hdrs req.body_size req.body + event on-frontend-http-request + +spoe-message coraza-res + args unique-id status res.ver res.hdrs res.body_size res.body + event on-http-response \ No newline at end of file diff --git a/doc/config/haproxy.cfg b/doc/config/haproxy.cfg new file mode 100644 index 0000000..4f471b8 --- /dev/null +++ b/doc/config/haproxy.cfg @@ -0,0 +1,28 @@ +# https://www.haproxy.com/documentation/hapee/latest/onepage/#home +global + log stdout format raw local0 + +defaults + timeout connect 5000ms + timeout client 5000ms + timeout server 5000ms + log global + +frontend coraza.io + mod http + bind *:80 + use_backend coraza.io.backend + unique-id-format %[uuid()] + unique-id-header X-Unique-ID + filter spoe engine coraza config coraza.cfg + http-request deny if { var(txn.coraza.fail) -m int eq 1 } + http-response deny if { var(txn.coraza.fail) -m int eq 1 } + +backend coraza.io.backend + mod http + server s1 127.0.0.1:8080 + server s2 127.0.0.1:9090 + +backend coraza-servers + mod tcp + server s1 127.0.0.1:9000 diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..1d5310d --- /dev/null +++ b/go.mod @@ -0,0 +1,15 @@ +module github.com/corazawaf/coraza-spoa + +go 1.17 + +require ( + go.uber.org/zap v1.21.0 + gopkg.in/natefinch/lumberjack.v2 v2.0.0 + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b +) + +require ( + github.com/BurntSushi/toml v1.0.0 // indirect + go.uber.org/atomic v1.7.0 // indirect + go.uber.org/multierr v1.6.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..ffdf7e6 --- /dev/null +++ b/go.sum @@ -0,0 +1,64 @@ +github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU= +github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= +go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/logger/field.go b/pkg/logger/field.go new file mode 100644 index 0000000..71932bd --- /dev/null +++ b/pkg/logger/field.go @@ -0,0 +1,180 @@ +// Copyright 2022 The Corazawaf Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package logger + +import "go.uber.org/zap" + +var ( + // Skip constructs a no-op field, which is often useful when handling invalid + // inputs in other Field constructors. + Skip = zap.Skip + // Binary constructs a field that carries an opaque binary blob. + // + // Binary data is serialized in an encoding-appropriate format. For example, + // zap's JSON encoder base64-encodes binary blobs. To log UTF-8 encoded text, + // use ByteString. + Binary = zap.Binary + // Bool constructs a field that carries a bool. + Bool = zap.Bool + // Boolp constructs a field that carries a *bool. The returned Field will safely + // and explicitly represent `nil` when appropriate. + Boolp = zap.Boolp + // ByteString constructs a field that carries UTF-8 encoded text as a []byte. + // To log opaque binary blobs (which aren't necessarily valid UTF-8), use + // Binary. + ByteString = zap.ByteString + // Complex128 constructs a field that carries a complex number. Unlike most + // numeric fields, this costs an allocation (to convert the complex128 to + // interface{}). + Complex128 = zap.Complex128 + // Complex128p constructs a field that carries a *complex128. The returned Field will safely + // and explicitly represent `nil` when appropriate. + Complex128p = zap.Complex128p + // Complex64 constructs a field that carries a complex number. Unlike most + // numeric fields, this costs an allocation (to convert the complex64 to + // interface{}). + Complex64 = zap.Complex64 + // Complex64p constructs a field that carries a *complex64. The returned Field will safely + // and explicitly represent `nil` when appropriate. + Complex64p = zap.Complex64p + // Float64 constructs a field that carries a float64. The way the + // floating-point value is represented is encoder-dependent, so marshaling is + // necessarily lazy. + Float64 = zap.Float64 + // Float64p constructs a field that carries a *float64. The returned Field will safely + // and explicitly represent `nil` when appropriate. + Float64p = zap.Float64p + // Float32 constructs a field that carries a float32. The way the + // floating-point value is represented is encoder-dependent, so marshaling is + // necessarily lazy. + Float32 = zap.Float32 + // Float32p constructs a field that carries a *float32. The returned Field will safely + // and explicitly represent `nil` when appropriate. + Float32p = zap.Float32p + // Int constructs a field with the given key and value. + Int = zap.Int + // Intp constructs a field that carries a *int. The returned Field will safely + // and explicitly represent `nil` when appropriate. + Intp = zap.Intp + // Int64 constructs a field with the given key and value. + Int64 = zap.Int64 + // Int64p constructs a field that carries a *int64. The returned Field will safely + // and explicitly represent `nil` when appropriate. + Int64p = zap.Int64p + // Int32 constructs a field with the given key and value. + Int32 = zap.Int32 + // Int32p constructs a field that carries a *int32. The returned Field will safely + // and explicitly represent `nil` when appropriate. + Int32p = zap.Int32p + // Int16 constructs a field with the given key and value. + Int16 = zap.Int16 + // Int16p constructs a field that carries a *int16. The returned Field will safely + // and explicitly represent `nil` when appropriate. + Int16p = zap.Int16p + // Int8 constructs a field with the given key and value. + Int8 = zap.Int8 + // Int8p constructs a field that carries a *int8. The returned Field will safely + // and explicitly represent `nil` when appropriate. + Int8p = zap.Int8p + // String constructs a field with the given key and value. + String = zap.String + // Stringp constructs a field that carries a *string. The returned Field will safely + // and explicitly represent `nil` when appropriate. + Stringp = zap.Stringp + // Uint constructs a field with the given key and value. + Uint = zap.Uint + // Uintp constructs a field that carries a *uint. The returned Field will safely + // and explicitly represent `nil` when appropriate. + Uintp = zap.Uintp + // Uint64 constructs a field with the given key and value. + Uint64 = zap.Uint64 + // Uint64p constructs a field that carries a *uint64. The returned Field will safely + // and explicitly represent `nil` when appropriate. + Uint64p = zap.Uint64p + // Uint32 constructs a field with the given key and value. + Uint32 = zap.Uint32 + // Uint32p constructs a field that carries a *uint32. The returned Field will safely + // and explicitly represent `nil` when appropriate. + Uint32p = zap.Uint32p + // Uint16 constructs a field with the given key and value. + Uint16 = zap.Uint16 + // Uint16p constructs a field that carries a *uint16. The returned Field will safely + // and explicitly represent `nil` when appropriate. + Uint16p = zap.Uint16p + // Uint8 constructs a field with the given key and value. + Uint8 = zap.Uint8 + // Uint8p constructs a field that carries a *uint8. The returned Field will safely + // and explicitly represent `nil` when appropriate. + Uint8p = zap.Uint8p + // Uintptr constructs a field with the given key and value. + Uintptr = zap.Uintptr + // Uintptrp constructs a field that carries a *uintptr. The returned Field will safely + // and explicitly represent `nil` when appropriate. + Uintptrp = zap.Uintptrp + // Reflect constructs a field with the given key and an arbitrary object. It uses + // an encoding-appropriate, reflection-based function to lazily serialize nearly + // any object into the logging context, but it's relatively slow and + // allocation-heavy. Outside tests, Any is always a better choice. + // + // If encoding fails (e.g., trying to serialize a map[int]string to JSON), Reflect + // includes the error message in the final log output. + Reflect = zap.Reflect + // Namespace creates a named, isolated scope within the logger's context. All + // subsequent fields will be added to the new namespace. + // + // This helps prevent key collisions when injecting loggers into sub-components + // or third-party libraries. + Namespace = zap.Namespace + // Stringer constructs a field with the given key and the output of the value's + // String method. The Stringer's String method is called lazily. + Stringer = zap.Stringer + // Time constructs a Field with the given key and value. The encoder + // controls how the time is serialized. + Time = zap.Time + // Timep constructs a field that carries a *time.Time. The returned Field will safely + // and explicitly represent `nil` when appropriate. + Timep = zap.Timep + // Stack constructs a field that stores a stacktrace of the current goroutine + // under provided key. Keep in mind that taking a stacktrace is eager and + // expensive (relatively speaking); this function both makes an allocation and + // takes about two microseconds. + Stack = zap.Stack + // StackSkip constructs a field similarly to Stack, but also skips the given + // number of frames from the top of the stacktrace. + StackSkip = zap.StackSkip + // Duration constructs a field with the given key and value. The encoder + // controls how the duration is serialized. + Duration = zap.Duration + // Durationp constructs a field that carries a *time.Duration. The returned Field will safely + // and explicitly represent `nil` when appropriate. + Durationp = zap.Durationp + // Object constructs a field with the given key and ObjectMarshaler. It + // provides a flexible, but still type-safe and efficient, way to add map- or + // struct-like user-defined types to the logging context. The struct's + // MarshalLogObject method is called lazily. + Object = zap.Object + // Inline constructs a Field that is similar to Object, but it + // will add the elements of the provided ObjectMarshaler to the + // current namespace. + Inline = zap.Inline + // Any takes a key and an arbitrary value and chooses the best way to represent + // them as a field, falling back to a reflection-based approach only if + // necessary. + // + // Since byte/uint8 and rune/int32 are aliases, Any can't differentiate between + // them. To minimize surprises, []byte values are treated as binary blobs, byte + // values are treated as uint8, and runes are always treated as integers. + Any = zap.Any +) diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go new file mode 100644 index 0000000..c8fbe38 --- /dev/null +++ b/pkg/logger/logger.go @@ -0,0 +1,124 @@ +// Copyright 2022 The Corazawaf Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package logger + +import ( + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "io" + "time" +) + +// A Level is a logging priority. Higher levels are more important. +type Level = zapcore.Level + +const ( + // DebugLevel logs are typically voluminous, and are usually disabled in + // production. + DebugLevel = zapcore.DebugLevel + // InfoLevel is the default logging priority. + InfoLevel = zapcore.InfoLevel + // WarnLevel logs are more important than Info, but don't need individual + // human review. + WarnLevel = zapcore.WarnLevel + // ErrorLevel logs are high-priority. If an application is running smoothly, + // it shouldn't generate any error-level logs. + ErrorLevel = zapcore.ErrorLevel + // PanicLevel logs a message, then panics. + PanicLevel = zapcore.PanicLevel + // FatalLevel logs a message, then calls os.Exit(1). + FatalLevel = zapcore.FatalLevel +) + +// ParseLevel parses a level based on the lower-case or all-caps ASCII +// representation of the log level. If the provided ASCII representation is +// invalid an error is returned. +// +// This is particularly useful when dealing with text input to configure log +// levels. +var ParseLevel = zapcore.ParseLevel + +// Field is an alias for Field. Aliasing this type dramatically +// improves the navigability of this package's API documentation. +type Field = zap.Field + +// Logger is an alias for zap.Logger. Aliasing this type dramatically +type Logger struct { + l *zap.Logger + level Level +} + +func (l *Logger) debug(msg string, fields ...Field) { + l.l.Debug(msg, fields...) +} + +func (l *Logger) info(msg string, fields ...Field) { + l.l.Info(msg, fields...) +} + +func (l *Logger) warn(msg string, fields ...Field) { + l.l.Warn(msg, fields...) +} + +func (l *Logger) error(msg string, fields ...Field) { + l.l.Error(msg, fields...) +} + +func (l *Logger) panic(msg string, fields ...Field) { + l.l.Panic(msg, fields...) +} + +func (l *Logger) fatal(msg string, fields ...Field) { + l.l.Fatal(msg, fields...) +} + +func (l *Logger) sync() error { + return l.l.Sync() +} + +// An Option configures a Logger. +type Option = zap.Option + +var ( + // WithCaller configures the Logger to annotate each message with the filename, + // line number, and function name of zap's caller, or not, depending on the + // value of enabled. This is a generalized form of AddCaller. + WithCaller = zap.WithCaller + // AddStacktrace configures the Logger to record a stack trace for all messages at + // or above a given level. + AddStacktrace = zap.AddStacktrace +) + +// New constructs a new Logger. +func New(writer io.Writer, level Level, ops ...Option) *Logger { + if writer == nil { + panic("The log writer is nil, please check it.") + } + + cfg := zap.NewProductionConfig() + cfg.EncoderConfig.EncodeTime = func(t time.Time, encoder zapcore.PrimitiveArrayEncoder) { + encoder.AppendString(t.Format("2006-01-02T15:04:05.000Z0700")) + } + core := zapcore.NewCore( + zapcore.NewJSONEncoder(cfg.EncoderConfig), + zapcore.AddSync(writer), + level, + ) + logger := &Logger{ + l: zap.New(core, ops...), + level: level, + } + return logger +} diff --git a/pkg/logger/method.go b/pkg/logger/method.go new file mode 100644 index 0000000..1bc79e3 --- /dev/null +++ b/pkg/logger/method.go @@ -0,0 +1,65 @@ +// Copyright 2022 The Corazawaf Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package logger + +import "os" + +var std = New(os.Stderr, InfoLevel, WithCaller(true)) + +var ( + // Debug logs a message at DebugLevel. The message includes any fields passed + // at the log site, as well as any fields accumulated on the logger. + Debug = std.debug + // Info logs a message at InfoLevel. The message includes any fields passed + // at the log site, as well as any fields accumulated on the logger. + Info = std.info + // Warn logs a message at WarnLevel. The message includes any fields passed + // at the log site, as well as any fields accumulated on the logger. + Warn = std.warn + // Error logs a message at ErrorLevel. The message includes any fields passed + // at the log site, as well as any fields accumulated on the logger. + Error = std.error + // Panic logs a message at PanicLevel. The message includes any fields passed + // at the log site, as well as any fields accumulated on the logger. + // + // The logger then panics, even if logging at PanicLevel is disabled. + Panic = std.panic + // Fatal logs a message at FatalLevel. The message includes any fields passed + // at the log site, as well as any fields accumulated on the logger. + // + // The logger then calls os.Exit(1), even if logging at FatalLevel is + // disabled. + Fatal = std.fatal +) + +// Reset resets the logger to its initial state. +func (l *Logger) Reset() { + std = l + Debug = std.debug + Info = std.info + Warn = std.warn + Error = std.error + Panic = std.panic + Fatal = std.fatal +} + +// Sync calls the underlying Core's Sync method, flushing any buffered log +// entries. Applications should take care to call Sync before exiting. +func Sync() error { + if std != nil { + return std.sync() + } + return nil +} diff --git a/pkg/logger/tee.go b/pkg/logger/tee.go new file mode 100644 index 0000000..daf07bb --- /dev/null +++ b/pkg/logger/tee.go @@ -0,0 +1,70 @@ +// Copyright 2022 The Corazawaf Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package logger + +import ( + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "gopkg.in/natefinch/lumberjack.v2" + "time" +) + +// LevelEnablerFunc is a function type that returns a bool indicating whether logging beyond a given Level should be enabled. +type LevelEnablerFunc func(Level) bool + +// RotateOptions used to configure the rotation of the log file. +type RotateOptions struct { + MaxSize int + MaxAge int + MaxBackups int + Compress bool +} + +// TeeOption used to configure the tee logger. +type TeeOption struct { + Filename string + ROpts RotateOptions + Lef LevelEnablerFunc +} + +// NewTeeWithRotate creates a new tee logger that contains multiple loggers with the rotation of the log function. +func NewTeeWithRotate(tops []TeeOption, opts ...Option) *Logger { + var cores []zapcore.Core + cfg := zap.NewProductionConfig() + cfg.EncoderConfig.EncodeTime = func(t time.Time, encoder zapcore.PrimitiveArrayEncoder) { + encoder.AppendString(t.Format("2006-01-02T15:04:05.000Z0700")) + } + for _, top := range tops { + w := zapcore.AddSync(&lumberjack.Logger{ + Filename: top.Filename, + MaxSize: top.ROpts.MaxSize, + MaxAge: top.ROpts.MaxAge, + MaxBackups: top.ROpts.MaxBackups, + Compress: top.ROpts.Compress, + }) + + core := zapcore.NewCore( + zapcore.NewConsoleEncoder(cfg.EncoderConfig), + zapcore.AddSync(w), + zap.LevelEnablerFunc(func(level zapcore.Level) bool { + return top.Lef(level) + }), + ) + cores = append(cores, core) + } + return &Logger{ + l: zap.New(zapcore.NewTee(cores...), opts...), + } +}