├── .github └── workflows │ ├── codeql.yaml │ ├── linting.yml │ └── test.yml ├── .golangci.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── _examples ├── cli │ └── cli.go ├── default │ └── default.go ├── iowriter │ └── main.go ├── logrus │ └── logrus.go ├── logrus_custom │ └── logrus.go └── testingstd │ ├── README.md │ └── testing_test.go ├── example_test.go ├── go.mod ├── go.sum ├── impl ├── cli │ ├── cli.go │ └── cli_test.go ├── logrus │ ├── logrus.go │ └── logrus_test.go ├── std │ ├── std.go │ └── std_test.go └── zap │ ├── zap.go │ └── zap_test.go ├── io ├── io.go └── io_test.go ├── log.go ├── log_test.go ├── std.go └── std_test.go /.github/workflows/codeql.yaml: -------------------------------------------------------------------------------- 1 | 2 | name: "CodeQL" 3 | 4 | on: 5 | push: 6 | branches: [main] 7 | pull_request: 8 | branches: [main] 9 | 10 | jobs: 11 | CodeQL-Build: 12 | runs-on: ubuntu-latest 13 | 14 | permissions: 15 | # required for all workflows 16 | security-events: write 17 | 18 | steps: 19 | - name: Checkout repository 20 | uses: actions/checkout@v3 21 | 22 | - name: Initialize CodeQL 23 | uses: github/codeql-action/init@v2 24 | with: 25 | languages: go 26 | 27 | - name: Autobuild 28 | uses: github/codeql-action/autobuild@v2 29 | 30 | - name: Perform CodeQL Analysis 31 | uses: github/codeql-action/analyze@v2 32 | -------------------------------------------------------------------------------- /.github/workflows/linting.yml: -------------------------------------------------------------------------------- 1 | name: golangci-lint 2 | on: 3 | push: 4 | tags: 5 | - v* 6 | branches: 7 | - main 8 | pull_request: 9 | jobs: 10 | golangci: 11 | name: lint 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - name: golangci-lint 16 | uses: golangci/golangci-lint-action@v3 17 | with: 18 | version: v1.52 19 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: 3 | push: 4 | tags: 5 | - v* 6 | branches: 7 | - main 8 | pull_request: 9 | jobs: 10 | test: 11 | strategy: 12 | matrix: 13 | go-version: ["1.19.x", "1.20.x"] 14 | os: [macos-latest, windows-latest, ubuntu-latest] 15 | runs-on: ${{ matrix.os }} 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v3 19 | - name: Setup Go for Building 20 | uses: actions/setup-go@v4 21 | with: 22 | go-version: ${{ matrix.go-version }} 23 | - name: Build 24 | run: go build -v ./... 25 | 26 | - name: Test 27 | run: go test -v ./... 28 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | deadline: 2m 3 | 4 | linters: 5 | disable-all: true 6 | enable: 7 | - misspell 8 | - govet 9 | - staticcheck 10 | - errcheck 11 | - unparam 12 | - ineffassign 13 | - nakedret 14 | - gocyclo 15 | - dupl 16 | - goimports 17 | - revive 18 | - gosec 19 | - gosimple 20 | - typecheck 21 | - unused 22 | 23 | linters-settings: 24 | dupl: 25 | threshold: 600 26 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guide 2 | 3 | We are happy you are interested in contributing. Before contributing, there 4 | are some things you should know. 5 | 6 | 1. If you are creating a new implementation please consider putting that in its 7 | own repository. We are happy to link to that from this repository. 8 | 2. This repository follows [semantic versioning](https://semver.org/). All public 9 | APIs (e.g. function names and arguments) can't change without incrementing 10 | the major version. Something that will be rare, if ever. 11 | 3. If you want to make some major changes to the reference implementations 12 | consider making another implementation in a separate repository. It's ok. Let 13 | us know and we'll provide a link to it in the README. 14 | 15 | ## Pull Requests 16 | 17 | All changes to this repository happen through pull requests on GitHub. If you 18 | have a proposed change it will need to be in the form of a pull request. 19 | 20 | When creating the pull request please provide a use case and justification for 21 | the change if it is for another more than a minor change. Changes to the 22 | functionality need a good justification. 23 | 24 | ## Develoers Certificate of Origin 25 | 26 | All changes to this repository need sign-off for the [Developers Certificate of Origin](https://developercertificate.org/). 27 | You can read it below. After that are some details on how to sign you work. 28 | 29 | Note, if you created a pull request and missed signing a commit CI will catch it 30 | and provide directions on how correct the issue. 31 | 32 | ``` 33 | Developer Certificate of Origin 34 | Version 1.1 35 | 36 | Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 37 | 1 Letterman Drive 38 | Suite D4700 39 | San Francisco, CA, 94129 40 | 41 | Everyone is permitted to copy and distribute verbatim copies of this 42 | license document, but changing it is not allowed. 43 | 44 | Developer's Certificate of Origin 1.1 45 | 46 | By making a contribution to this project, I certify that: 47 | 48 | (a) The contribution was created in whole or in part by me and I 49 | have the right to submit it under the open source license 50 | indicated in the file; or 51 | 52 | (b) The contribution is based upon previous work that, to the best 53 | of my knowledge, is covered under an appropriate open source 54 | license and I have the right under that license to submit that 55 | work with modifications, whether created in whole or in part 56 | by me, under the same open source license (unless I am 57 | permitted to submit under a different license), as indicated 58 | in the file; or 59 | 60 | (c) The contribution was provided directly to me by some other 61 | person who certified (a), (b) or (c) and I have not modified 62 | it. 63 | 64 | (d) I understand and agree that this project and the contribution 65 | are public and that a record of the contribution (including all 66 | personal information I submit with it, including my sign-off) is 67 | maintained indefinitely and may be redistributed consistent with 68 | this project or the open source license(s) involved. 69 | ``` 70 | 71 | Then you just add a line to every git commit message: 72 | 73 | Signed-off-by: Joe Smith 74 | 75 | Use your real name (sorry, no pseudonyms or anonymous contributions.) 76 | 77 | If you set your `user.name` and `user.email` git configs, you can sign your commit automatically 78 | with `git commit -s`. 79 | 80 | Note: If your git config information is set properly then viewing the `git log` information for your 81 | commit will look something like this: 82 | 83 | ``` 84 | Author: Joe Smith 85 | Date: Thu Feb 2 11:41:15 2018 -0800 86 | 87 | Update README 88 | 89 | Signed-off-by: Joe Smith 90 | ``` 91 | 92 | Notice the `Author` and `Signed-off-by` lines match. If they don't your PR will be rejected by the 93 | automated DCO check. 94 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Log Package 2 | 3 | The log package provides a common interface for logging that can be used in 4 | applications and libraries along with reference implementations for logrus, zap, 5 | the Go standard library package, and for a CLI (console app). 6 | 7 | [![Go Report Card](https://goreportcard.com/badge/github.com/Masterminds/log-go)](https://goreportcard.com/report/github.com/Masterminds/log-go) 8 | [![](https://github.com/Masterminds/semver/workflows/Tests/badge.svg)](https://github.com/Masterminds/log-go/actions) 9 | [![GoDoc](https://img.shields.io/static/v1?label=go.dev&message=reference&color=blue)](https://pkg.go.dev/github.com/Masterminds/log-go) 10 | 11 | ## Why A Log Interface 12 | 13 | In many programming languages there is a logging interface. Libraries will use 14 | use the interface for logging and then the application will choose the logging 15 | library of their choice for the application as a whole. Since everything follows 16 | the interface it works. 17 | 18 | Go does not have a detailed interface. The logging package included in the 19 | standard library has been insufficient so many logging libraries have been 20 | created. 21 | 22 | Various libraries/packages use the a logging implementation of their choice. 23 | When those are pulled into a larger application is will end up having multiple 24 | different logging systems. The main application needs to wire them all up or it 25 | will miss some logs. If you take a look at applications like Kubernetes, Nomad, 26 | and many others you'll discover they import many different logging implementations. 27 | 28 | Using interfaces provides a may to move away from multiple implementation, 29 | simplify codebases, reduce binary size, and reduce threat surface area. 30 | 31 | ## What This Package Provides 32 | 33 | This library includes several things including: 34 | 35 | - A Go interface for leveled logging. Those levels include - Fatal, Panic, Error, 36 | Warn, Info, Debug, Trace 37 | - When logging messages the interface can do a message, a message with formatting 38 | string and arguments, and a message with fields (key/value pairs) 39 | - Package level logging functions whose implementation can be changed/set 40 | - Reference implementations for logrus, zap, the standard library, and a CLI 41 | - A simple default implementation so it just works for library testing and 42 | simple situations 43 | 44 | ## Usage 45 | 46 | The usage documentation is broken down into 3 types of usage depending on your 47 | situation. These examples are for library/package authors, applications, and 48 | logger implementation developers. 49 | 50 | ### Library / Package Authors 51 | 52 | If you are a library or package author there are two ways you can use this log 53 | package. 54 | 55 | First, you can import the package and use the package level logging options. For 56 | example: 57 | 58 | ```go 59 | import( 60 | "github.com/Masterminds/log-go" 61 | ) 62 | 63 | log.Info("Send Some Info") 64 | ``` 65 | 66 | You can use this for logging within your package. 67 | 68 | Second, if you want to pass a logger around your package you can use the 69 | interface provided by this package. For example: 70 | 71 | ```go 72 | import "github.com/Masterminds/log-go" 73 | 74 | func NewConstructorExample(logger log.Logger) { 75 | return &Example{ 76 | logger: logger, 77 | } 78 | } 79 | 80 | func (e *Example) Foo() { 81 | e.logger.Info("Send Some Info") 82 | } 83 | 84 | ``` 85 | 86 | In your packages testing you can check log messages if you need to see that they 87 | are working and contain what you are looking for. A simple example of doing this 88 | is in the `_examples` directory. 89 | 90 | For details on exactly which functions are on the package or on the `Logger` 91 | interface, please see the [package docs](https://pkg.go.dev/github.com/Masterminds/log-go). 92 | 93 | ### Application Developers 94 | 95 | If you are developing an application that will be writing logs you will want to 96 | setup and configure logging the way you want. This is where this interface 97 | based package empowers you. You can pick your logging implementation or write 98 | your own. 99 | 100 | For example, if you want to use a standard logrus logger you can setup logging 101 | like so: 102 | 103 | ```go 104 | import( 105 | "github.com/Masterminds/log-go" 106 | "github.com/Masterminds/log-go/impl/logrus" 107 | ) 108 | 109 | log.Current = logrus.NewStandard() 110 | ``` 111 | 112 | In this example a standard logrus logger is created, wrapped in a struct 113 | instance that conforms to the interface, and is set as the global logger to use. 114 | 115 | The `impl` directory has several reference implementations and they have 116 | configurable setups. 117 | 118 | Once you setup the global logger the way you want all the packages will use this 119 | same logger. 120 | 121 | ### Logger Developers 122 | 123 | There are many loggers and many ways to record logs. They can be written to a 124 | file, sent to stdout/stderr, sent to a logging service, and more. Each of these 125 | is possible with this package. 126 | 127 | If you have logger you want to use you can write one that conforms to the 128 | `Logger` interface found in `log.go`. That logger can then be configured as 129 | documented in the previous section. 130 | 131 | The `impl` directory has reference implementations you can look at for further 132 | examples. 133 | 134 | ## Log Levels 135 | 136 | The following log levels are currently implemented across the interface and all 137 | the reference implementations. 138 | 139 | - Fatal 140 | - Panic 141 | - Error 142 | - Warn 143 | - Info 144 | - Debug 145 | - Trace 146 | -------------------------------------------------------------------------------- /_examples/cli/cli.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/Masterminds/log-go" 5 | "github.com/Masterminds/log-go/impl/cli" 6 | ) 7 | 8 | func main() { 9 | lgr := cli.NewStandard() 10 | lgr.Level = log.TraceLevel 11 | 12 | log.Current = lgr 13 | 14 | // A basic message 15 | log.Info("Hello,", "World") 16 | 17 | // A trace message 18 | log.Trace("A low level trace message") 19 | 20 | // A trace message 21 | log.Debugf("Hello, %s", "World") 22 | 23 | // Use Go formatting on a warning 24 | log.Warnf("Foo %s", "bar") 25 | 26 | // An error with context 27 | log.Errorw("foo, bar", log.Fields{"baz": "qux"}) 28 | } 29 | -------------------------------------------------------------------------------- /_examples/default/default.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/Masterminds/log-go" 4 | 5 | func main() { 6 | 7 | // A basic message 8 | log.Info("Hello,", "World") 9 | 10 | // Use Go formatting on a warning 11 | log.Warnf("Foo %s", "bar") 12 | 13 | // An error with context 14 | log.Errorw("foo, bar", log.Fields{"baz": "qux"}) 15 | } 16 | -------------------------------------------------------------------------------- /_examples/iowriter/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/Masterminds/log-go" 7 | logio "github.com/Masterminds/log-go/io" 8 | ) 9 | 10 | // Sometimes you have an io.Writer interface and you want to have it write to 11 | // the logs. Since loggers do things a variety of ways it is difficult to ask 12 | // each of them to expose an underlying io.Writer. It is easier to be able to 13 | // generate one that leverages the log.Logger api. The following is an example 14 | // that does just that. 15 | 16 | func main() { 17 | // Get an io.Writer compliant instance that will write messages at the info 18 | // level. This uses the Current configured logger. 19 | w := logio.NewCurrentWriter(log.InfoLevel) 20 | 21 | // io.WriteString accepts an io.Writer as its first argument. This shows 22 | // writing the string "foo" to the logger at the info level. 23 | _, _ = io.WriteString(w, "foo") 24 | } 25 | -------------------------------------------------------------------------------- /_examples/logrus/logrus.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/Masterminds/log-go" 5 | "github.com/Masterminds/log-go/impl/logrus" 6 | ) 7 | 8 | func main() { 9 | log.Current = logrus.NewStandard() 10 | 11 | // A basic message 12 | log.Info("Hello,", "World") 13 | 14 | // Use Go formatting on a warning 15 | log.Warnf("Foo %s", "bar") 16 | 17 | // An error with context 18 | log.Errorw("foo, bar", log.Fields{"baz": "qux"}) 19 | } 20 | -------------------------------------------------------------------------------- /_examples/logrus_custom/logrus.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/Masterminds/log-go" 5 | lgrs "github.com/Masterminds/log-go/impl/logrus" 6 | "github.com/sirupsen/logrus" 7 | ) 8 | 9 | func main() { 10 | var lg = logrus.New() 11 | lg.Level = logrus.DebugLevel 12 | 13 | log.Current = lgrs.New(lg) 14 | 15 | log.Debug("Debugging!") 16 | 17 | // A basic message 18 | log.Info("Hello,", "World") 19 | 20 | // Use Go formatting on a warning 21 | log.Warnf("Foo %s", "bar") 22 | 23 | // An error with context 24 | log.Errorw("foo, bar", log.Fields{"baz": "qux"}) 25 | } 26 | -------------------------------------------------------------------------------- /_examples/testingstd/README.md: -------------------------------------------------------------------------------- 1 | # Testing with Std Lib Example 2 | 3 | When using a logger it is common to write tests to verify correct handling in 4 | an application. One way to do that is to use a buffer to collect the log 5 | messages and then check the content of the buffer. 6 | 7 | The code in this example showcases using the Go standard library logger in 8 | a testing setup with a buffer to collect the messages. -------------------------------------------------------------------------------- /_examples/testingstd/testing_test.go: -------------------------------------------------------------------------------- 1 | package testingstd 2 | 3 | import ( 4 | "bytes" 5 | stdlog "log" 6 | "strings" 7 | "testing" 8 | 9 | "github.com/Masterminds/log-go" 10 | "github.com/Masterminds/log-go/impl/std" 11 | ) 12 | 13 | // This example is designed to help you understand how you can write tests with 14 | // the interface and implementations. 15 | // For further examples, each of the implementations in the impl directory has 16 | // tests associated with you that check for expected output. You can use those 17 | // as additional examples. 18 | // This example uses the logger included in the standard library. The same idea 19 | // and capability is included for the other loggers in the impl directory. 20 | 21 | func TestLogger(t *testing.T) { 22 | 23 | // Create a logger that uses a buffer to capture the logging output 24 | var ( 25 | buf bytes.Buffer 26 | logger = stdlog.New(&buf, "", stdlog.Lshortfile) 27 | ) 28 | 29 | // The impl/std package enables you to pass in an instance of a logger from 30 | // the standard library. The github.com/Masterminds/log-go.NewStandard() 31 | // constructor uses the standard logger in the standard libraries log pkg. 32 | lgr := std.New(logger) 33 | 34 | // Set the appropriate log level for use in the tests. Info is the default. 35 | lgr.Level = log.DebugLevel 36 | 37 | // Try out the Fibonacci generator that uses the passed in logger. Ignoring 38 | // the result as we aren't testing the result. 39 | lgrfib(lgr, 1) 40 | 41 | // See if the buffer the logger is writing to has the right response 42 | if !strings.Contains(buf.String(), "Number is 1") { 43 | t.Error("buffer did not contain the right log message") 44 | } 45 | 46 | // Empty the buffer before the next test. 47 | buf.Reset() 48 | 49 | // The package level logger can be tested as well. 50 | // First, save the current package level logger, setup a defer, and replace 51 | // it. 52 | prev := log.Current 53 | defer func() { 54 | log.Current = prev 55 | }() 56 | log.Current = lgr 57 | 58 | // Try out the Fibonacci generator that uses package level logger 59 | fib(1) 60 | 61 | // See if the buffer the logger is writing to has the right response 62 | // In this case we check the log level that was written to the log 63 | if !strings.Contains(buf.String(), "[DEBUG] Number is 1") { 64 | t.Error("buffer did not contain the right log message") 65 | } 66 | 67 | // Empty the buffer before the next test. 68 | buf.Reset() 69 | 70 | } 71 | 72 | // A basic Fibonacci generator that logs the number passed in using the logger 73 | // configured as the Current one for package log package level functions. 74 | func fib(num uint) uint { 75 | log.Debugf("Number is %d", num) 76 | 77 | if num <= 1 { 78 | return num 79 | } 80 | 81 | return fib(num-1) + fib(num-2) 82 | } 83 | 84 | // A basic Fibonacci generator that logs the number passed in using the passed 85 | // in logger that conforms to the log.Logger interface. 86 | func lgrfib(lgr log.Logger, num uint) uint { 87 | lgr.Debugf("Number is %d", num) 88 | 89 | if num <= 1 { 90 | return num 91 | } 92 | 93 | return lgrfib(lgr, num-1) + lgrfib(lgr, num-2) 94 | } 95 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | package log_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/Masterminds/log-go" 7 | "github.com/Masterminds/log-go/impl/logrus" 8 | ) 9 | 10 | type Foo struct { 11 | Logger log.Logger 12 | } 13 | 14 | func (f *Foo) DoSomething() { 15 | f.Logger.Info("Hello Logging") 16 | } 17 | 18 | func Example() { 19 | 20 | // Using the default Logger 21 | log.Info("Hello") 22 | log.Error("World") 23 | 24 | // Create a logrus logger with default configuration that uses the log 25 | // interface. Note, logrus can be setup with default settings or setup with 26 | // custom settings using a second constructor. 27 | lgrs := logrus.NewStandard() 28 | 29 | // Set logrus as the global logger 30 | log.Current = lgrs 31 | 32 | // Logrus is now used globally for logging 33 | log.Warn("Warning through logrus") 34 | 35 | f1 := Foo{ 36 | Logger: lgrs, 37 | } 38 | 39 | // Logging in DoSomething will use the set logger which is logrus 40 | f1.DoSomething() 41 | 42 | f2 := Foo{ 43 | // The log package uses the global logger from the standard library log 44 | // package. A custom standard library logger can be used with the 45 | // github.com/Masterminds/log-go/impl/std package. 46 | Logger: log.NewStandard(), 47 | } 48 | 49 | // Logging in DoSomething will the logger from the standard library 50 | f2.DoSomething() 51 | 52 | // Need to detect the logger being used? You can check for the type. 53 | switch log.Current.(type) { 54 | case *log.StdLogger: 55 | fmt.Println("The default logger") 56 | case *logrus.Logger: 57 | fmt.Printf("Logrus is used for logging") 58 | default: 59 | fmt.Printf("Something else that implements the interface") 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Masterminds/log-go 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/fatih/color v1.15.0 7 | github.com/sirupsen/logrus v1.9.0 8 | github.com/stretchr/testify v1.8.2 9 | go.uber.org/zap v1.24.0 10 | ) 11 | 12 | require ( 13 | github.com/benbjohnson/clock v1.3.0 // indirect 14 | github.com/davecgh/go-spew v1.1.1 // indirect 15 | github.com/mattn/go-colorable v0.1.13 // indirect 16 | github.com/mattn/go-isatty v0.0.18 // indirect 17 | github.com/pmezard/go-difflib v1.0.0 // indirect 18 | go.uber.org/atomic v1.10.0 // indirect 19 | go.uber.org/multierr v1.11.0 // indirect 20 | golang.org/x/sys v0.7.0 // indirect 21 | gopkg.in/yaml.v3 v3.0.1 // indirect 22 | ) 23 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= 2 | github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= 7 | github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= 8 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 9 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 10 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 11 | github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= 12 | github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 13 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 14 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 15 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 16 | github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= 17 | github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 18 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 19 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 20 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 21 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 22 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 23 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 24 | github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= 25 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 26 | go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= 27 | go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= 28 | go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= 29 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 30 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 31 | go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= 32 | go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= 33 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 34 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 35 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 36 | golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= 37 | golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 38 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 39 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 40 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 41 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 42 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 43 | -------------------------------------------------------------------------------- /impl/cli/cli.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | 8 | "github.com/Masterminds/log-go" 9 | "github.com/fatih/color" 10 | ) 11 | 12 | // TODO: Add i18n support for level labels 13 | // TODO: Add a mutex to lock writing output 14 | 15 | // This package provides a reference implementation for a CLI logger, where the 16 | // output is written to the console. If you need a CLI logger that is fairly 17 | // different from this one please feel free to create another CLI implementation 18 | // and you can fork this one as a starting point. Not everyone needs to use this 19 | // logger CLI implementation and it does not need to have all features. 20 | 21 | // Logger provides a CLI based logger. Log messages are written to the CLI as 22 | // terminal style output. 23 | type Logger struct { 24 | // TraceOut provides a writer to write trace messages to 25 | TraceOut io.Writer 26 | 27 | // DebugOut provides a writer to write debug messages to 28 | DebugOut io.Writer 29 | 30 | // InfoOut provides a writer to write info messages to 31 | InfoOut io.Writer 32 | 33 | // WarnOut provides a writer to write warning messages to 34 | WarnOut io.Writer 35 | 36 | // ErrorOut provides a writer to write error messages to 37 | ErrorOut io.Writer 38 | 39 | // PanicOut provides a writer to write panic messages to 40 | PanicOut io.Writer 41 | 42 | // FatalOut provides a writer to write fatal messages to 43 | FatalOut io.Writer 44 | 45 | // Level sets the current logging level 46 | Level int 47 | 48 | // Colors used for each of the levels. Note, the Info level intentionally 49 | // does not have a color. It will use the tty default. 50 | TraceColor *color.Color 51 | DebugColor *color.Color 52 | WarnColor *color.Color 53 | ErrorColor *color.Color 54 | PanicColor *color.Color 55 | FatalColor *color.Color 56 | } 57 | 58 | // NewStandard creates a default CLI logger 59 | func NewStandard() *Logger { 60 | Red := color.New(color.FgRed) 61 | RedB := color.New(color.FgRed, color.Bold) 62 | Yellow := color.New(color.FgYellow) 63 | Blue := color.New(color.FgBlue) 64 | Green := color.New(color.FgGreen) 65 | 66 | return &Logger{ 67 | // Note, stderr is used for all non-info messages by default 68 | TraceOut: os.Stderr, 69 | DebugOut: os.Stderr, 70 | InfoOut: os.Stdout, 71 | WarnOut: os.Stderr, 72 | ErrorOut: os.Stderr, 73 | PanicOut: os.Stderr, 74 | FatalOut: os.Stderr, 75 | Level: log.InfoLevel, 76 | TraceColor: Green, 77 | DebugColor: Blue, 78 | WarnColor: Yellow, 79 | ErrorColor: Red, 80 | PanicColor: RedB, 81 | FatalColor: RedB, 82 | } 83 | } 84 | 85 | // Trace logs a message at the Trace level 86 | func (l Logger) Trace(msg ...interface{}) { 87 | if l.Level <= log.TraceLevel { 88 | out := fmt.Sprint(append([]interface{}{l.TraceColor.Sprint("TRACE: ")}, msg...)...) 89 | out = checkEnding(out) 90 | fmt.Fprint(l.TraceOut, out) 91 | } 92 | } 93 | 94 | // Tracef formats a message according to a format specifier and logs the 95 | // message at the Trace level 96 | func (l Logger) Tracef(template string, args ...interface{}) { 97 | if l.Level <= log.TraceLevel { 98 | out := l.TraceColor.Sprint("TRACE: ") 99 | out += fmt.Sprintf(template, args...) 100 | out = checkEnding(out) 101 | fmt.Fprint(l.TraceOut, out) 102 | } 103 | } 104 | 105 | // Tracew logs a message at the Trace level along with some additional 106 | // context (key-value pairs) 107 | func (l Logger) Tracew(msg string, fields log.Fields) { 108 | if l.Level <= log.TraceLevel { 109 | out := l.TraceColor.Sprint("TRACE: ") 110 | out += fmt.Sprint(msg, handlFields(fields)) 111 | out = checkEnding(out) 112 | fmt.Fprint(l.TraceOut, out) 113 | } 114 | } 115 | 116 | // Debug logs a message at the Debug level 117 | func (l Logger) Debug(msg ...interface{}) { 118 | if l.Level <= log.DebugLevel { 119 | out := fmt.Sprint(append([]interface{}{l.DebugColor.Sprint("DEBUG: ")}, msg...)...) 120 | out = checkEnding(out) 121 | fmt.Fprint(l.DebugOut, out) 122 | } 123 | } 124 | 125 | // Debugf formats a message according to a format specifier and logs the 126 | // message at the Debug level 127 | func (l Logger) Debugf(template string, args ...interface{}) { 128 | if l.Level <= log.DebugLevel { 129 | out := l.DebugColor.Sprint("DEBUG: ") 130 | out += fmt.Sprintf(template, args...) 131 | out = checkEnding(out) 132 | fmt.Fprint(l.DebugOut, out) 133 | } 134 | } 135 | 136 | // Debugw logs a message at the Debug level along with some additional 137 | // context (key-value pairs) 138 | func (l Logger) Debugw(msg string, fields log.Fields) { 139 | if l.Level <= log.DebugLevel { 140 | out := l.DebugColor.Sprint("DEBUG: ") 141 | out += fmt.Sprint(msg, handlFields(fields)) 142 | out = checkEnding(out) 143 | fmt.Fprint(l.DebugOut, out) 144 | } 145 | } 146 | 147 | // Info logs a message at the Info level 148 | func (l Logger) Info(msg ...interface{}) { 149 | if l.Level <= log.InfoLevel { 150 | out := fmt.Sprint(msg...) 151 | out = checkEnding(out) 152 | fmt.Fprint(l.InfoOut, out) 153 | } 154 | } 155 | 156 | // Infof formats a message according to a format specifier and logs the 157 | // message at the Info level 158 | func (l Logger) Infof(template string, args ...interface{}) { 159 | if l.Level <= log.InfoLevel { 160 | out := fmt.Sprintf(template, args...) 161 | out = checkEnding(out) 162 | fmt.Fprint(l.InfoOut, out) 163 | } 164 | } 165 | 166 | // Infow logs a message at the Info level along with some additional 167 | // context (key-value pairs) 168 | func (l Logger) Infow(msg string, fields log.Fields) { 169 | if l.Level <= log.InfoLevel { 170 | out := fmt.Sprint(msg, handlFields(fields)) 171 | out = checkEnding(out) 172 | fmt.Fprint(l.InfoOut, out) 173 | } 174 | } 175 | 176 | // Warn logs a message at the Warn level 177 | func (l Logger) Warn(msg ...interface{}) { 178 | if l.Level <= log.WarnLevel { 179 | out := l.WarnColor.Sprint(append([]interface{}{"WARNING: "}, msg...)...) 180 | out = checkEnding(out) 181 | fmt.Fprint(l.WarnOut, out) 182 | } 183 | } 184 | 185 | // Warnf formats a message according to a format specifier and logs the 186 | // message at the Warning level 187 | func (l Logger) Warnf(template string, args ...interface{}) { 188 | if l.Level <= log.WarnLevel { 189 | out := l.WarnColor.Sprintf("WARNING: "+template, args...) 190 | out = checkEnding(out) 191 | fmt.Fprint(l.WarnOut, out) 192 | } 193 | } 194 | 195 | // Warnw logs a message at the Warning level along with some additional 196 | // context (key-value pairs) 197 | func (l Logger) Warnw(msg string, fields log.Fields) { 198 | if l.Level <= log.WarnLevel { 199 | out := l.WarnColor.Sprint("WARNING: "+msg, handlFields(fields)) 200 | out = checkEnding(out) 201 | fmt.Fprint(l.WarnOut, out) 202 | } 203 | } 204 | 205 | // Error logs a message at the Error level 206 | func (l Logger) Error(msg ...interface{}) { 207 | if l.Level <= log.ErrorLevel { 208 | out := l.ErrorColor.Sprint(append([]interface{}{"ERROR: "}, msg...)...) 209 | out = checkEnding(out) 210 | fmt.Fprint(l.ErrorOut, out) 211 | } 212 | } 213 | 214 | // Errorf formats a message according to a format specifier and logs the 215 | // message at the Error level 216 | func (l Logger) Errorf(template string, args ...interface{}) { 217 | if l.Level <= log.ErrorLevel { 218 | out := l.ErrorColor.Sprintf("ERROR: "+template, args...) 219 | out = checkEnding(out) 220 | fmt.Fprint(l.ErrorOut, out) 221 | } 222 | } 223 | 224 | // Errorw logs a message at the Error level along with some additional 225 | // context (key-value pairs) 226 | func (l Logger) Errorw(msg string, fields log.Fields) { 227 | if l.Level <= log.ErrorLevel { 228 | out := l.ErrorColor.Sprint("ERROR: "+msg, handlFields(fields)) 229 | out = checkEnding(out) 230 | fmt.Fprint(l.ErrorOut, out) 231 | } 232 | } 233 | 234 | // Panic logs a message at the Panic level and panics 235 | func (l Logger) Panic(msg ...interface{}) { 236 | if l.Level <= log.PanicLevel { 237 | out := l.PanicColor.Sprint(append([]interface{}{"PANIC: "}, msg...)...) 238 | out = checkEnding(out) 239 | fmt.Fprint(l.PanicOut, out) 240 | panic(out) 241 | } 242 | } 243 | 244 | // Panicf formats a message according to a format specifier and logs the 245 | // message at the Panic level and then panics 246 | func (l Logger) Panicf(template string, args ...interface{}) { 247 | if l.Level <= log.PanicLevel { 248 | out := l.PanicColor.Sprintf("PANIC: "+template, args...) 249 | out = checkEnding(out) 250 | fmt.Fprint(l.PanicOut, out) 251 | panic(out) 252 | } 253 | } 254 | 255 | // Panicw logs a message at the Panic level along with some additional 256 | // context (key-value pairs) and then panics 257 | func (l Logger) Panicw(msg string, fields log.Fields) { 258 | if l.Level <= log.PanicLevel { 259 | out := l.PanicColor.Sprint("PANIC: "+msg, handlFields(fields)) 260 | out = checkEnding(out) 261 | fmt.Fprint(l.PanicOut, out) 262 | panic(out) 263 | } 264 | } 265 | 266 | // Fatal logs a message at the Fatal level and exists the application 267 | func (l Logger) Fatal(msg ...interface{}) { 268 | if l.Level <= log.FatalLevel { 269 | out := l.FatalColor.Sprint(append([]interface{}{"FATAL: "}, msg...)...) 270 | out = checkEnding(out) 271 | fmt.Fprint(l.FatalOut, out) 272 | os.Exit(1) 273 | } 274 | } 275 | 276 | // Fatalf formats a message according to a format specifier and logs the 277 | // message at the Fatal level and exits the application 278 | func (l Logger) Fatalf(template string, args ...interface{}) { 279 | if l.Level <= log.FatalLevel { 280 | out := l.FatalColor.Sprintf("FATAL: "+template, args...) 281 | out = checkEnding(out) 282 | fmt.Fprint(l.FatalOut, out) 283 | os.Exit(1) 284 | } 285 | } 286 | 287 | // Fatalw logs a message at the Fatal level along with some additional 288 | // context (key-value pairs) and exits the application 289 | func (l Logger) Fatalw(msg string, fields log.Fields) { 290 | if l.Level <= log.FatalLevel { 291 | out := l.FatalColor.Sprint("FATAL: "+msg, handlFields(fields)) 292 | out = checkEnding(out) 293 | fmt.Fprint(l.FatalOut, out) 294 | os.Exit(1) 295 | } 296 | } 297 | 298 | func handlFields(flds log.Fields) string { 299 | ret := " " 300 | for k, v := range flds { 301 | ret += fmt.Sprintf("%s=%s ", k, v) 302 | } 303 | return ret 304 | } 305 | 306 | func checkEnding(in string) string { 307 | if len(in) == 0 || in[len(in)-1] != '\n' { 308 | return in + "\n" 309 | } 310 | return in 311 | } 312 | -------------------------------------------------------------------------------- /impl/cli/cli_test.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "bytes" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/Masterminds/log-go" 9 | "github.com/fatih/color" 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestLogger(t *testing.T) { 14 | 15 | // Test the logger meets the interface 16 | var _ log.Logger = new(Logger) 17 | 18 | black := color.New(color.FgBlack) 19 | 20 | buf := &bytes.Buffer{} 21 | lgr := &Logger{ 22 | TraceOut: buf, 23 | DebugOut: buf, 24 | InfoOut: buf, 25 | WarnOut: buf, 26 | ErrorOut: buf, 27 | PanicOut: buf, 28 | FatalOut: buf, 29 | Level: log.TraceLevel, 30 | TraceColor: black, 31 | DebugColor: black, 32 | WarnColor: black, 33 | ErrorColor: black, 34 | PanicColor: black, 35 | FatalColor: black, 36 | } 37 | 38 | lgr.Trace("test trace") 39 | if !strings.Contains(buf.String(), `test trace`) { 40 | t.Log(buf.String()) 41 | t.Error("cli trace not logging correctly") 42 | } 43 | buf.Reset() 44 | 45 | lgr.Tracef("Hello %s", "World") 46 | if !strings.Contains(buf.String(), `Hello World`) { 47 | t.Error("cli trace not logging correctly") 48 | } 49 | buf.Reset() 50 | 51 | lgr.Tracew("foo bar", log.Fields{"baz": "qux"}) 52 | if !strings.Contains(buf.String(), `foo bar baz=qux`) { 53 | t.Log(buf.String()) 54 | t.Error("cli trace not logging correctly") 55 | } 56 | buf.Reset() 57 | 58 | lgr.Debug("test debug") 59 | if !strings.Contains(buf.String(), `test debug`) { 60 | t.Log(buf.String()) 61 | t.Error("cli debug not logging correctly") 62 | } 63 | buf.Reset() 64 | 65 | lgr.Debugf("Hello %s", "World") 66 | if !strings.Contains(buf.String(), `Hello World`) { 67 | t.Error("cli debug not logging correctly") 68 | } 69 | buf.Reset() 70 | 71 | lgr.Debugw("foo bar", log.Fields{"baz": "qux"}) 72 | if !strings.Contains(buf.String(), `foo bar baz=qux`) { 73 | t.Log(buf.String()) 74 | t.Error("cli debug not logging correctly") 75 | } 76 | buf.Reset() 77 | 78 | lgr.Info("test info") 79 | if !strings.Contains(buf.String(), `test info`) { 80 | t.Error("cli info not logging correctly") 81 | } 82 | buf.Reset() 83 | 84 | lgr.Infof("Hello %s", "World") 85 | if !strings.Contains(buf.String(), `Hello World`) { 86 | t.Error("cli info not logging correctly") 87 | } 88 | buf.Reset() 89 | 90 | lgr.Infow("foo bar", log.Fields{"baz": "qux"}) 91 | if !strings.Contains(buf.String(), `foo bar baz=qux`) { 92 | t.Log(buf.String()) 93 | t.Error("cli info not logging correctly") 94 | } 95 | buf.Reset() 96 | 97 | lgr.Warn("test warn") 98 | if !strings.Contains(buf.String(), `test warn`) { 99 | t.Log(buf.String()) 100 | t.Error("cli warn not logging correctly") 101 | } 102 | buf.Reset() 103 | 104 | lgr.Warnf("Hello %s", "World") 105 | if !strings.Contains(buf.String(), `Hello World`) { 106 | t.Log(buf.String()) 107 | t.Error("cli warn not logging correctly") 108 | } 109 | buf.Reset() 110 | 111 | lgr.Warnw("foo bar", log.Fields{"baz": "qux"}) 112 | if !strings.Contains(buf.String(), `foo bar baz=qux`) { 113 | t.Log(buf.String()) 114 | t.Error("cli warn not logging correctly") 115 | } 116 | buf.Reset() 117 | 118 | lgr.Error("test error") 119 | if !strings.Contains(buf.String(), `test error`) { 120 | t.Log(buf.String()) 121 | t.Error("cli error not logging correctly") 122 | } 123 | buf.Reset() 124 | 125 | lgr.Errorf("Hello %s", "World") 126 | if !strings.Contains(buf.String(), `Hello World`) { 127 | t.Log(buf.String()) 128 | t.Error("cli error not logging correctly") 129 | } 130 | buf.Reset() 131 | 132 | lgr.Errorw("foo bar", log.Fields{"baz": "qux"}) 133 | if !strings.Contains(buf.String(), `foo bar baz=qux`) { 134 | t.Log(buf.String()) 135 | t.Error("cli error not logging correctly") 136 | } 137 | buf.Reset() 138 | 139 | assert.PanicsWithValue(t, "PANIC: test panic\n", func() { 140 | lgr.Panic("test panic") 141 | }) 142 | if !strings.Contains(buf.String(), `test panic`) { 143 | t.Log(buf.String()) 144 | t.Error("cli panic not logging correctly") 145 | } 146 | buf.Reset() 147 | 148 | assert.PanicsWithValue(t, "PANIC: Hello World\n", func() { 149 | lgr.Panicf("Hello %s", "World") 150 | }) 151 | if !strings.Contains(buf.String(), `Hello World`) { 152 | t.Log(buf.String()) 153 | t.Error("cli panic not logging correctly") 154 | } 155 | buf.Reset() 156 | 157 | assert.PanicsWithValue(t, "PANIC: foo bar baz=qux \n", func() { 158 | lgr.Panicw("foo bar", log.Fields{"baz": "qux"}) 159 | }) 160 | if !strings.Contains(buf.String(), `foo bar baz=qux`) { 161 | t.Log(buf.String()) 162 | t.Error("cli panic not logging correctly") 163 | } 164 | buf.Reset() 165 | 166 | // lgr.Fatal("test fatal") 167 | // lgr.Fatalf(template string, args ...interface{}) 168 | // lgr.Fatalw(msg string, fields Fields) 169 | 170 | } 171 | -------------------------------------------------------------------------------- /impl/logrus/logrus.go: -------------------------------------------------------------------------------- 1 | package logrus 2 | 3 | import ( 4 | "github.com/Masterminds/log-go" 5 | "github.com/sirupsen/logrus" 6 | ) 7 | 8 | // Logger is a logger that wraps the logrus logger and has it conform to the 9 | // log.Logger interface 10 | type Logger struct { 11 | logger *logrus.Logger 12 | } 13 | 14 | // New takes an existing logrus logger and uses that for logging 15 | func New(lgr *logrus.Logger) *Logger { 16 | return &Logger{ 17 | logger: lgr, 18 | } 19 | } 20 | 21 | // NewStandard returns a logger with a logrus standard logger which it 22 | // instantiates 23 | func NewStandard() *Logger { 24 | return &Logger{ 25 | logger: logrus.StandardLogger(), 26 | } 27 | } 28 | 29 | // Trace logs a message at the Trace level 30 | func (l Logger) Trace(msg ...interface{}) { 31 | l.logger.Trace(msg...) 32 | } 33 | 34 | // Tracef formats a message according to a format specifier and logs the 35 | // message at the Trace level 36 | func (l Logger) Tracef(template string, args ...interface{}) { 37 | l.logger.Tracef(template, args...) 38 | } 39 | 40 | // Tracew logs a message at the Trace level along with some additional 41 | // context (key-value pairs) 42 | func (l Logger) Tracew(msg string, fields log.Fields) { 43 | l.logger.WithFields(logrus.Fields(fields)).Trace(msg) 44 | } 45 | 46 | // Debug logs a message at the Debug level 47 | func (l Logger) Debug(msg ...interface{}) { 48 | l.logger.Debug(msg...) 49 | } 50 | 51 | // Debugf formats a message according to a format specifier and logs the 52 | // message at the Debug level 53 | func (l Logger) Debugf(template string, args ...interface{}) { 54 | l.logger.Debugf(template, args...) 55 | } 56 | 57 | // Debugw logs a message at the Debug level along with some additional 58 | // context (key-value pairs) 59 | func (l Logger) Debugw(msg string, fields log.Fields) { 60 | l.logger.WithFields(logrus.Fields(fields)).Debug(msg) 61 | } 62 | 63 | // Info logs a message at the Info level 64 | func (l Logger) Info(msg ...interface{}) { 65 | l.logger.Info(msg...) 66 | } 67 | 68 | // Infof formats a message according to a format specifier and logs the 69 | // message at the Info level 70 | func (l Logger) Infof(template string, args ...interface{}) { 71 | l.logger.Infof(template, args...) 72 | } 73 | 74 | // Infow logs a message at the Info level along with some additional 75 | // context (key-value pairs) 76 | func (l Logger) Infow(msg string, fields log.Fields) { 77 | l.logger.WithFields(logrus.Fields(fields)).Info(msg) 78 | } 79 | 80 | // Warn logs a message at the Warn level 81 | func (l Logger) Warn(msg ...interface{}) { 82 | l.logger.Warn(msg...) 83 | } 84 | 85 | // Warnf formats a message according to a format specifier and logs the 86 | // message at the Warning level 87 | func (l Logger) Warnf(template string, args ...interface{}) { 88 | l.logger.Warnf(template, args...) 89 | } 90 | 91 | // Warnw logs a message at the Warning level along with some additional 92 | // context (key-value pairs) 93 | func (l Logger) Warnw(msg string, fields log.Fields) { 94 | l.logger.WithFields(logrus.Fields(fields)).Warn(msg) 95 | } 96 | 97 | // Error logs a message at the Error level 98 | func (l Logger) Error(msg ...interface{}) { 99 | l.logger.Error(msg...) 100 | } 101 | 102 | // Errorf formats a message according to a format specifier and logs the 103 | // message at the Error level 104 | func (l Logger) Errorf(template string, args ...interface{}) { 105 | l.logger.Errorf(template, args...) 106 | } 107 | 108 | // Errorw logs a message at the Error level along with some additional 109 | // context (key-value pairs) 110 | func (l Logger) Errorw(msg string, fields log.Fields) { 111 | l.logger.WithFields(logrus.Fields(fields)).Error(msg) 112 | } 113 | 114 | // Panic logs a message at the Panic level and panics 115 | func (l Logger) Panic(msg ...interface{}) { 116 | l.logger.Panic(msg...) 117 | } 118 | 119 | // Panicf formats a message according to a format specifier and logs the 120 | // message at the Panic level and then panics 121 | func (l Logger) Panicf(template string, args ...interface{}) { 122 | l.logger.Panicf(template, args...) 123 | } 124 | 125 | // Panicw logs a message at the Panic level along with some additional 126 | // context (key-value pairs) and then panics 127 | func (l Logger) Panicw(msg string, fields log.Fields) { 128 | l.logger.WithFields(logrus.Fields(fields)).Panic(msg) 129 | } 130 | 131 | // Fatal logs a message at the Fatal level and exists the application 132 | func (l Logger) Fatal(msg ...interface{}) { 133 | l.logger.Fatal(msg...) 134 | } 135 | 136 | // Fatalf formats a message according to a format specifier and logs the 137 | // message at the Fatal level and exits the application 138 | func (l Logger) Fatalf(template string, args ...interface{}) { 139 | l.logger.Fatalf(template, args...) 140 | } 141 | 142 | // Fatalw logs a message at the Fatal level along with some additional 143 | // context (key-value pairs) and exits the application 144 | func (l Logger) Fatalw(msg string, fields log.Fields) { 145 | l.logger.WithFields(logrus.Fields(fields)).Fatal(msg) 146 | } 147 | -------------------------------------------------------------------------------- /impl/logrus/logrus_test.go: -------------------------------------------------------------------------------- 1 | package logrus 2 | 3 | import ( 4 | "bytes" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/Masterminds/log-go" 9 | "github.com/sirupsen/logrus" 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestLogrus(t *testing.T) { 14 | 15 | // Test the logger meets the interface 16 | var _ log.Logger = new(Logger) 17 | 18 | var logger = logrus.New() 19 | logger.SetLevel(logrus.TraceLevel) 20 | buf := &bytes.Buffer{} 21 | logger.SetOutput(buf) 22 | lgr := New(logger) 23 | 24 | lgr.Trace("test trace") 25 | if !strings.Contains(buf.String(), `level=trace msg="test trace"`) { 26 | t.Error("logrus trace not logging correctly") 27 | } 28 | buf.Reset() 29 | 30 | lgr.Tracef("Hello %s", "World") 31 | if !strings.Contains(buf.String(), `level=trace msg="Hello World"`) { 32 | t.Error("logrus trace not logging correctly") 33 | } 34 | buf.Reset() 35 | 36 | lgr.Tracew("foo bar", log.Fields{"baz": "qux"}) 37 | if !strings.Contains(buf.String(), `level=trace msg="foo bar" baz=qux`) { 38 | t.Log(buf.String()) 39 | t.Error("logrus trace not logging correctly") 40 | } 41 | buf.Reset() 42 | 43 | lgr.Debug("test debug") 44 | if !strings.Contains(buf.String(), `level=debug msg="test debug"`) { 45 | t.Error("logrus debug not logging correctly") 46 | } 47 | buf.Reset() 48 | 49 | lgr.Debugf("Hello %s", "World") 50 | if !strings.Contains(buf.String(), `level=debug msg="Hello World"`) { 51 | t.Error("logrus debug not logging correctly") 52 | } 53 | buf.Reset() 54 | 55 | lgr.Debugw("foo bar", log.Fields{"baz": "qux"}) 56 | if !strings.Contains(buf.String(), `level=debug msg="foo bar" baz=qux`) { 57 | t.Log(buf.String()) 58 | t.Error("logrus info not logging correctly") 59 | } 60 | buf.Reset() 61 | 62 | lgr.Info("test info") 63 | if !strings.Contains(buf.String(), `level=info msg="test info"`) { 64 | t.Error("logrus info not logging correctly") 65 | } 66 | buf.Reset() 67 | 68 | lgr.Infof("Hello %s", "World") 69 | if !strings.Contains(buf.String(), `level=info msg="Hello World"`) { 70 | t.Error("logrus info not logging correctly") 71 | } 72 | buf.Reset() 73 | 74 | lgr.Infow("foo bar", log.Fields{"baz": "qux"}) 75 | if !strings.Contains(buf.String(), `level=info msg="foo bar" baz=qux`) { 76 | t.Log(buf.String()) 77 | t.Error("logrus info not logging correctly") 78 | } 79 | buf.Reset() 80 | 81 | lgr.Warn("test warn") 82 | if !strings.Contains(buf.String(), `level=warning msg="test warn"`) { 83 | t.Log(buf.String()) 84 | t.Error("logrus warn not logging correctly") 85 | } 86 | buf.Reset() 87 | 88 | lgr.Warnf("Hello %s", "World") 89 | if !strings.Contains(buf.String(), `level=warning msg="Hello World"`) { 90 | t.Log(buf.String()) 91 | t.Error("logrus warn not logging correctly") 92 | } 93 | buf.Reset() 94 | 95 | lgr.Warnw("foo bar", log.Fields{"baz": "qux"}) 96 | if !strings.Contains(buf.String(), `level=warning msg="foo bar" baz=qux`) { 97 | t.Log(buf.String()) 98 | t.Error("logrus warn not logging correctly") 99 | } 100 | buf.Reset() 101 | 102 | lgr.Error("test error") 103 | if !strings.Contains(buf.String(), `level=error msg="test error"`) { 104 | t.Log(buf.String()) 105 | t.Error("logrus error not logging correctly") 106 | } 107 | buf.Reset() 108 | 109 | lgr.Errorf("Hello %s", "World") 110 | if !strings.Contains(buf.String(), `level=error msg="Hello World"`) { 111 | t.Log(buf.String()) 112 | t.Error("logrus error not logging correctly") 113 | } 114 | buf.Reset() 115 | 116 | lgr.Errorw("foo bar", log.Fields{"baz": "qux"}) 117 | if !strings.Contains(buf.String(), `level=error msg="foo bar" baz=qux`) { 118 | t.Log(buf.String()) 119 | t.Error("logrus error not logging correctly") 120 | } 121 | buf.Reset() 122 | 123 | // lgr.Fatal("test fatal") 124 | // lgr.Fatalf(template string, args ...interface{}) 125 | // lgr.Fatalw(msg string, fields Fields) 126 | 127 | } 128 | 129 | func TestStandardLogrus(t *testing.T) { 130 | logrus.SetLevel(logrus.DebugLevel) 131 | buf := &bytes.Buffer{} 132 | logrus.SetOutput(buf) 133 | lgr := NewStandard() 134 | 135 | lgr.Debug("test debug") 136 | if !strings.Contains(buf.String(), `level=debug msg="test debug"`) { 137 | t.Error("logrus debug not logging correctly") 138 | } 139 | buf.Reset() 140 | 141 | lgr.Debugf("Hello %s", "World") 142 | if !strings.Contains(buf.String(), `level=debug msg="Hello World"`) { 143 | t.Error("logrus debug not logging correctly") 144 | } 145 | buf.Reset() 146 | 147 | lgr.Debugw("foo bar", log.Fields{"baz": "qux"}) 148 | if !strings.Contains(buf.String(), `level=debug msg="foo bar" baz=qux`) { 149 | t.Log(buf.String()) 150 | t.Error("logrus info not logging correctly") 151 | } 152 | buf.Reset() 153 | 154 | lgr.Info("test info") 155 | if !strings.Contains(buf.String(), `level=info msg="test info"`) { 156 | t.Error("logrus info not logging correctly") 157 | } 158 | buf.Reset() 159 | 160 | lgr.Infof("Hello %s", "World") 161 | if !strings.Contains(buf.String(), `level=info msg="Hello World"`) { 162 | t.Error("logrus info not logging correctly") 163 | } 164 | buf.Reset() 165 | 166 | lgr.Infow("foo bar", log.Fields{"baz": "qux"}) 167 | if !strings.Contains(buf.String(), `level=info msg="foo bar" baz=qux`) { 168 | t.Log(buf.String()) 169 | t.Error("logrus info not logging correctly") 170 | } 171 | buf.Reset() 172 | 173 | lgr.Warn("test warn") 174 | if !strings.Contains(buf.String(), `level=warning msg="test warn"`) { 175 | t.Log(buf.String()) 176 | t.Error("logrus warn not logging correctly") 177 | } 178 | buf.Reset() 179 | 180 | lgr.Warnf("Hello %s", "World") 181 | if !strings.Contains(buf.String(), `level=warning msg="Hello World"`) { 182 | t.Log(buf.String()) 183 | t.Error("logrus warn not logging correctly") 184 | } 185 | buf.Reset() 186 | 187 | lgr.Warnw("foo bar", log.Fields{"baz": "qux"}) 188 | if !strings.Contains(buf.String(), `level=warning msg="foo bar" baz=qux`) { 189 | t.Log(buf.String()) 190 | t.Error("logrus warn not logging correctly") 191 | } 192 | buf.Reset() 193 | 194 | lgr.Error("test error") 195 | if !strings.Contains(buf.String(), `level=error msg="test error"`) { 196 | t.Log(buf.String()) 197 | t.Error("logrus error not logging correctly") 198 | } 199 | buf.Reset() 200 | 201 | lgr.Errorf("Hello %s", "World") 202 | if !strings.Contains(buf.String(), `level=error msg="Hello World"`) { 203 | t.Log(buf.String()) 204 | t.Error("logrus error not logging correctly") 205 | } 206 | buf.Reset() 207 | 208 | lgr.Errorw("foo bar", log.Fields{"baz": "qux"}) 209 | if !strings.Contains(buf.String(), `level=error msg="foo bar" baz=qux`) { 210 | t.Log(buf.String()) 211 | t.Error("logrus error not logging correctly") 212 | } 213 | buf.Reset() 214 | 215 | assert.Panics(t, func() { 216 | lgr.Panic("test panic") 217 | }) 218 | if !strings.Contains(buf.String(), `level=panic msg="test panic"`) { 219 | t.Log(buf.String()) 220 | t.Error("cli panic not logging correctly") 221 | } 222 | buf.Reset() 223 | 224 | assert.Panics(t, func() { 225 | lgr.Panicf("Hello %s", "World") 226 | }) 227 | if !strings.Contains(buf.String(), `level=panic msg="Hello World"`) { 228 | t.Log(buf.String()) 229 | t.Error("cli panic not logging correctly") 230 | } 231 | buf.Reset() 232 | 233 | assert.Panics(t, func() { 234 | lgr.Panicw("foo bar", log.Fields{"baz": "qux"}) 235 | }) 236 | if !strings.Contains(buf.String(), `level=panic msg="foo bar" baz=qux`) { 237 | t.Log(buf.String()) 238 | t.Error("cli panic not logging correctly") 239 | } 240 | buf.Reset() 241 | 242 | // lgr.Fatal("test fatal") 243 | // lgr.Fatalf(template string, args ...interface{}) 244 | // lgr.Fatalw(msg string, fields Fields) 245 | 246 | } 247 | 248 | func TestLogrusInterface(_ *testing.T) { 249 | lgr := NewStandard() 250 | testfunc(lgr) 251 | } 252 | 253 | func testfunc(l log.Logger) { 254 | l.Debug("test") 255 | } 256 | -------------------------------------------------------------------------------- /impl/std/std.go: -------------------------------------------------------------------------------- 1 | package std 2 | 3 | import ( 4 | "fmt" 5 | stdlog "log" 6 | 7 | "github.com/Masterminds/log-go" 8 | ) 9 | 10 | // New creates an instance of std.Logger that wraps a logger from the standard 11 | // library. It takes a logger from the standard library as an argument. This 12 | // is now it differs from log.NewStandard() which is used by default. The 13 | // logger is configurable 14 | func New(lgr *stdlog.Logger) *Logger { 15 | return &Logger{ 16 | logger: lgr, 17 | Level: log.InfoLevel, 18 | } 19 | } 20 | 21 | // Logger is a wrapper around an instance of a logger from the Go standard 22 | // library 23 | type Logger struct { 24 | logger *stdlog.Logger 25 | Level int 26 | } 27 | 28 | // Trace logs a message at the Trace level 29 | func (l Logger) Trace(msg ...interface{}) { 30 | if l.Level <= log.TraceLevel { 31 | l.logger.Print(append([]interface{}{"[TRACE] "}, msg...)...) 32 | } 33 | } 34 | 35 | // Tracef formats a message according to a format specifier and logs the 36 | // message at the Trace level 37 | func (l Logger) Tracef(template string, args ...interface{}) { 38 | if l.Level <= log.TraceLevel { 39 | l.logger.Printf("[TRACE] "+template, args...) 40 | } 41 | } 42 | 43 | // Tracew logs a message at the Trace level along with some additional 44 | // context (key-value pairs) 45 | func (l Logger) Tracew(msg string, fields log.Fields) { 46 | if l.Level <= log.TraceLevel { 47 | l.logger.Printf("[TRACE] %s %s", msg, handlFields(fields)) 48 | } 49 | } 50 | 51 | // Debug logs a message at the Debug level 52 | func (l Logger) Debug(msg ...interface{}) { 53 | if l.Level <= log.DebugLevel { 54 | l.logger.Print(append([]interface{}{"[DEBUG] "}, msg...)...) 55 | } 56 | } 57 | 58 | // Debugf formats a message according to a format specifier and logs the 59 | // message at the Debug level 60 | func (l Logger) Debugf(template string, args ...interface{}) { 61 | if l.Level <= log.DebugLevel { 62 | l.logger.Printf("[DEBUG] "+template, args...) 63 | } 64 | } 65 | 66 | // Debugw logs a message at the Debug level along with some additional 67 | // context (key-value pairs) 68 | func (l Logger) Debugw(msg string, fields log.Fields) { 69 | if l.Level <= log.DebugLevel { 70 | l.logger.Printf("[DEBUG] %s %s", msg, handlFields(fields)) 71 | } 72 | } 73 | 74 | // Info logs a message at the Info level 75 | func (l Logger) Info(msg ...interface{}) { 76 | if l.Level <= log.InfoLevel { 77 | l.logger.Print(append([]interface{}{"[INFO] "}, msg...)...) 78 | } 79 | } 80 | 81 | // Infof formats a message according to a format specifier and logs the 82 | // message at the Info level 83 | func (l Logger) Infof(template string, args ...interface{}) { 84 | if l.Level <= log.InfoLevel { 85 | l.logger.Printf("[INFO] "+template, args...) 86 | } 87 | } 88 | 89 | // Infow logs a message at the Info level along with some additional 90 | // context (key-value pairs) 91 | func (l Logger) Infow(msg string, fields log.Fields) { 92 | if l.Level <= log.InfoLevel { 93 | l.logger.Printf("[INFO] %s %s", msg, handlFields(fields)) 94 | } 95 | } 96 | 97 | // Warn logs a message at the Warn level 98 | func (l Logger) Warn(msg ...interface{}) { 99 | if l.Level <= log.WarnLevel { 100 | l.logger.Print(append([]interface{}{"[WARNING] "}, msg...)...) 101 | } 102 | } 103 | 104 | // Warnf formats a message according to a format specifier and logs the 105 | // message at the Warning level 106 | func (l Logger) Warnf(template string, args ...interface{}) { 107 | if l.Level <= log.WarnLevel { 108 | l.logger.Printf("[WARNING] "+template, args...) 109 | } 110 | } 111 | 112 | // Warnw logs a message at the Warning level along with some additional 113 | // context (key-value pairs) 114 | func (l Logger) Warnw(msg string, fields log.Fields) { 115 | if l.Level <= log.WarnLevel { 116 | l.logger.Printf("[WARNING] %s %s", msg, handlFields(fields)) 117 | } 118 | } 119 | 120 | // Error logs a message at the Error level 121 | func (l Logger) Error(msg ...interface{}) { 122 | if l.Level <= log.ErrorLevel { 123 | l.logger.Print(append([]interface{}{"[ERROR] "}, msg...)...) 124 | } 125 | } 126 | 127 | // Errorf formats a message according to a format specifier and logs the 128 | // message at the Error level 129 | func (l Logger) Errorf(template string, args ...interface{}) { 130 | if l.Level <= log.ErrorLevel { 131 | l.logger.Printf("[ERROR] "+template, args...) 132 | } 133 | } 134 | 135 | // Errorw logs a message at the Error level along with some additional 136 | // context (key-value pairs) 137 | func (l Logger) Errorw(msg string, fields log.Fields) { 138 | if l.Level <= log.ErrorLevel { 139 | l.logger.Printf("[ERROR] %s %s", msg, handlFields(fields)) 140 | } 141 | } 142 | 143 | // Panic logs a message at the Panic level and panics 144 | func (l Logger) Panic(msg ...interface{}) { 145 | if l.Level <= log.PanicLevel { 146 | l.logger.Panic(append([]interface{}{"[PANIC] "}, msg...)...) 147 | } 148 | } 149 | 150 | // Panicf formats a message according to a format specifier and logs the 151 | // message at the Panic level and then panics 152 | func (l Logger) Panicf(template string, args ...interface{}) { 153 | if l.Level <= log.PanicLevel { 154 | l.logger.Panicf("[PANIC] "+template, args...) 155 | } 156 | } 157 | 158 | // Panicw logs a message at the Panic level along with some additional 159 | // context (key-value pairs) and then panics 160 | func (l Logger) Panicw(msg string, fields log.Fields) { 161 | if l.Level <= log.PanicLevel { 162 | l.logger.Panicf("[PANIC] %s %s", msg, handlFields(fields)) 163 | } 164 | } 165 | 166 | // Fatal logs a message at the Fatal level and exists the application 167 | func (l Logger) Fatal(msg ...interface{}) { 168 | if l.Level <= log.FatalLevel { 169 | l.logger.Fatal(append([]interface{}{"[FATAL] "}, msg...)...) 170 | } 171 | } 172 | 173 | // Fatalf formats a message according to a format specifier and logs the 174 | // message at the Fatal level and exits the application 175 | func (l Logger) Fatalf(template string, args ...interface{}) { 176 | if l.Level <= log.FatalLevel { 177 | l.logger.Fatalf("[FATAL] "+template, args...) 178 | } 179 | } 180 | 181 | // Fatalw logs a message at the Fatal level along with some additional 182 | // context (key-value pairs) and exits the application 183 | func (l Logger) Fatalw(msg string, fields log.Fields) { 184 | if l.Level <= log.FatalLevel { 185 | l.logger.Fatalf("[FATAL] %s %s", msg, handlFields(fields)) 186 | } 187 | } 188 | 189 | func handlFields(flds log.Fields) string { 190 | var ret string 191 | for k, v := range flds { 192 | ret += fmt.Sprintf("[%s=%s]", k, v) 193 | } 194 | return ret 195 | } 196 | -------------------------------------------------------------------------------- /impl/std/std_test.go: -------------------------------------------------------------------------------- 1 | package std 2 | 3 | import ( 4 | "bytes" 5 | stdlog "log" 6 | "strings" 7 | "testing" 8 | 9 | "github.com/Masterminds/log-go" 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestLogger(t *testing.T) { 14 | 15 | // Test the logger meets the interface 16 | var _ log.Logger = new(Logger) 17 | 18 | var ( 19 | buf bytes.Buffer 20 | logger = stdlog.New(&buf, "", stdlog.Lshortfile) 21 | ) 22 | lgr := New(logger) 23 | 24 | // Make sure levels are working 25 | lgr.Debug("test debug") 26 | if strings.Contains(buf.String(), `[DEBUG] test debug`) { 27 | t.Log(buf.String()) 28 | t.Error("stdlib debug not logging correctly") 29 | } 30 | buf.Reset() 31 | 32 | // Test all levels 33 | lgr.Level = log.TraceLevel 34 | 35 | lgr.Trace("test trace") 36 | if !strings.Contains(buf.String(), `[TRACE] test trace`) { 37 | t.Log(buf.String()) 38 | t.Error("stdlib trace not logging correctly") 39 | } 40 | buf.Reset() 41 | 42 | lgr.Tracef("Hello %s", "World") 43 | if !strings.Contains(buf.String(), `[TRACE] Hello World`) { 44 | t.Error("stdlib trace not logging correctly") 45 | } 46 | buf.Reset() 47 | 48 | lgr.Tracew("foo bar", log.Fields{"baz": "qux"}) 49 | if !strings.Contains(buf.String(), `[TRACE] foo bar [baz=qux]`) { 50 | t.Log(buf.String()) 51 | t.Error("stdlib trace not logging correctly") 52 | } 53 | buf.Reset() 54 | 55 | lgr.Debug("test debug") 56 | if !strings.Contains(buf.String(), `[DEBUG] test debug`) { 57 | t.Log(buf.String()) 58 | t.Error("stdlib debug not logging correctly") 59 | } 60 | buf.Reset() 61 | 62 | lgr.Debugf("Hello %s", "World") 63 | if !strings.Contains(buf.String(), `[DEBUG] Hello World`) { 64 | t.Error("stdlib debug not logging correctly") 65 | } 66 | buf.Reset() 67 | 68 | lgr.Debugw("foo bar", log.Fields{"baz": "qux"}) 69 | if !strings.Contains(buf.String(), `[DEBUG] foo bar [baz=qux]`) { 70 | t.Log(buf.String()) 71 | t.Error("stdlib info not logging correctly") 72 | } 73 | buf.Reset() 74 | 75 | lgr.Info("test info") 76 | if !strings.Contains(buf.String(), `[INFO] test info`) { 77 | t.Error("stdlib info not logging correctly") 78 | } 79 | buf.Reset() 80 | 81 | lgr.Infof("Hello %s", "World") 82 | if !strings.Contains(buf.String(), `[INFO] Hello World`) { 83 | t.Error("stdlib info not logging correctly") 84 | } 85 | buf.Reset() 86 | 87 | lgr.Infow("foo bar", log.Fields{"baz": "qux"}) 88 | if !strings.Contains(buf.String(), `[INFO] foo bar [baz=qux]`) { 89 | t.Log(buf.String()) 90 | t.Error("stdlib info not logging correctly") 91 | } 92 | buf.Reset() 93 | 94 | lgr.Warn("test warn") 95 | if !strings.Contains(buf.String(), `[WARNING] test warn`) { 96 | t.Log(buf.String()) 97 | t.Error("stdlib warn not logging correctly") 98 | } 99 | buf.Reset() 100 | 101 | lgr.Warnf("Hello %s", "World") 102 | if !strings.Contains(buf.String(), `[WARNING] Hello World`) { 103 | t.Log(buf.String()) 104 | t.Error("stdlib warn not logging correctly") 105 | } 106 | buf.Reset() 107 | 108 | lgr.Warnw("foo bar", log.Fields{"baz": "qux"}) 109 | if !strings.Contains(buf.String(), `[WARNING] foo bar [baz=qux]`) { 110 | t.Log(buf.String()) 111 | t.Error("stdlib warn not logging correctly") 112 | } 113 | buf.Reset() 114 | 115 | lgr.Error("test error") 116 | if !strings.Contains(buf.String(), `[ERROR] test error`) { 117 | t.Log(buf.String()) 118 | t.Error("stdlib error not logging correctly") 119 | } 120 | buf.Reset() 121 | 122 | lgr.Errorf("Hello %s", "World") 123 | if !strings.Contains(buf.String(), `[ERROR] Hello World`) { 124 | t.Log(buf.String()) 125 | t.Error("stdlib error not logging correctly") 126 | } 127 | buf.Reset() 128 | 129 | lgr.Errorw("foo bar", log.Fields{"baz": "qux"}) 130 | if !strings.Contains(buf.String(), `[ERROR] foo bar [baz=qux]`) { 131 | t.Log(buf.String()) 132 | t.Error("stdlib error not logging correctly") 133 | } 134 | buf.Reset() 135 | 136 | assert.PanicsWithValue(t, "[PANIC] test panic", func() { 137 | lgr.Panic("test panic") 138 | }) 139 | if !strings.Contains(buf.String(), `test panic`) { 140 | t.Log(buf.String()) 141 | t.Error("cli panic not logging correctly") 142 | } 143 | buf.Reset() 144 | 145 | assert.PanicsWithValue(t, "[PANIC] Hello World", func() { 146 | lgr.Panicf("Hello %s", "World") 147 | }) 148 | if !strings.Contains(buf.String(), `Hello World`) { 149 | t.Log(buf.String()) 150 | t.Error("cli panic not logging correctly") 151 | } 152 | buf.Reset() 153 | 154 | assert.PanicsWithValue(t, "[PANIC] foo bar [baz=qux]", func() { 155 | lgr.Panicw("foo bar", log.Fields{"baz": "qux"}) 156 | }) 157 | if !strings.Contains(buf.String(), `foo bar [baz=qux]`) { 158 | t.Log(buf.String()) 159 | t.Error("cli panic not logging correctly") 160 | } 161 | buf.Reset() 162 | 163 | // lgr.Fatal("test fatal") 164 | // lgr.Fatalf(template string, args ...interface{}) 165 | // lgr.Fatalw(msg string, fields Fields) 166 | 167 | } 168 | -------------------------------------------------------------------------------- /impl/zap/zap.go: -------------------------------------------------------------------------------- 1 | package zap 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/Masterminds/log-go" 7 | "go.uber.org/zap" 8 | "go.uber.org/zap/zapcore" 9 | ) 10 | 11 | // New creates an instance of Zap that wraps a zap unsugared logger. It takes 12 | // a preconfigured zap logger as an argument 13 | func New(lgr *zap.Logger) *Logger { 14 | return &Logger{ 15 | logger: lgr, 16 | } 17 | } 18 | 19 | // Logger is a wrapper about a Zap logger that implements the log.Logger interface 20 | type Logger struct { 21 | logger *zap.Logger 22 | } 23 | 24 | // Trace logs a message at the Trace level 25 | func (l Logger) Trace(msg ...interface{}) { 26 | if ce := l.logger.Check(zap.DebugLevel-1, fmt.Sprint(msg...)); ce != nil { 27 | ce.Write() 28 | } 29 | } 30 | 31 | // Tracef formats a message according to a format specifier and logs the 32 | // message at the Trace level 33 | func (l Logger) Tracef(template string, args ...interface{}) { 34 | if ce := l.logger.Check(zap.DebugLevel-1, fmt.Sprintf(template, args...)); ce != nil { 35 | ce.Write() 36 | } 37 | } 38 | 39 | // Tracew logs a message at the Trace level along with some additional 40 | // context (key-value pairs) 41 | func (l Logger) Tracew(msg string, fields log.Fields) { 42 | if ce := l.logger.Check(zap.DebugLevel-1, msg); ce != nil { 43 | ce.Write(fieldToAny(fields)...) 44 | } 45 | } 46 | 47 | // Debug logs a message at the Debug level 48 | func (l Logger) Debug(msg ...interface{}) { 49 | if ce := l.logger.Check(zap.DebugLevel, fmt.Sprint(msg...)); ce != nil { 50 | ce.Write() 51 | } 52 | } 53 | 54 | // Debugf formats a message according to a format specifier and logs the 55 | // message at the Debug level 56 | func (l Logger) Debugf(template string, args ...interface{}) { 57 | if ce := l.logger.Check(zap.DebugLevel, fmt.Sprintf(template, args...)); ce != nil { 58 | ce.Write() 59 | } 60 | } 61 | 62 | // Debugw logs a message at the Debug level along with some additional 63 | // context (key-value pairs) 64 | func (l Logger) Debugw(msg string, fields log.Fields) { 65 | if ce := l.logger.Check(zap.DebugLevel, msg); ce != nil { 66 | ce.Write(fieldToAny(fields)...) 67 | } 68 | } 69 | 70 | // Info logs a message at the Info level 71 | func (l Logger) Info(msg ...interface{}) { 72 | if ce := l.logger.Check(zap.InfoLevel, fmt.Sprint(msg...)); ce != nil { 73 | ce.Write() 74 | } 75 | } 76 | 77 | // Infof formats a message according to a format specifier and logs the 78 | // message at the Info level 79 | func (l Logger) Infof(template string, args ...interface{}) { 80 | if ce := l.logger.Check(zap.InfoLevel, fmt.Sprintf(template, args...)); ce != nil { 81 | ce.Write() 82 | } 83 | } 84 | 85 | // Infow logs a message at the Info level along with some additional 86 | // context (key-value pairs) 87 | func (l Logger) Infow(msg string, fields log.Fields) { 88 | if ce := l.logger.Check(zap.InfoLevel, msg); ce != nil { 89 | ce.Write(fieldToAny(fields)...) 90 | } 91 | } 92 | 93 | // Warn logs a message at the Warn level 94 | func (l Logger) Warn(msg ...interface{}) { 95 | if ce := l.logger.Check(zap.WarnLevel, fmt.Sprint(msg...)); ce != nil { 96 | ce.Write() 97 | } 98 | } 99 | 100 | // Warnf formats a message according to a format specifier and logs the 101 | // message at the Warning level 102 | func (l Logger) Warnf(template string, args ...interface{}) { 103 | if ce := l.logger.Check(zap.WarnLevel, fmt.Sprintf(template, args...)); ce != nil { 104 | ce.Write() 105 | } 106 | } 107 | 108 | // Warnw logs a message at the Warning level along with some additional 109 | // context (key-value pairs) 110 | func (l Logger) Warnw(msg string, fields log.Fields) { 111 | if ce := l.logger.Check(zap.WarnLevel, msg); ce != nil { 112 | ce.Write(fieldToAny(fields)...) 113 | } 114 | } 115 | 116 | // Error logs a message at the Error level 117 | func (l Logger) Error(msg ...interface{}) { 118 | if ce := l.logger.Check(zap.ErrorLevel, fmt.Sprint(msg...)); ce != nil { 119 | ce.Write() 120 | } 121 | } 122 | 123 | // Errorf formats a message according to a format specifier and logs the 124 | // message at the Error level 125 | func (l Logger) Errorf(template string, args ...interface{}) { 126 | if ce := l.logger.Check(zap.ErrorLevel, fmt.Sprintf(template, args...)); ce != nil { 127 | ce.Write() 128 | } 129 | } 130 | 131 | // Errorw logs a message at the Error level along with some additional 132 | // context (key-value pairs) 133 | func (l Logger) Errorw(msg string, fields log.Fields) { 134 | if ce := l.logger.Check(zap.ErrorLevel, msg); ce != nil { 135 | ce.Write(fieldToAny(fields)...) 136 | } 137 | } 138 | 139 | // Panic logs a message at the Panic level and panics 140 | func (l Logger) Panic(msg ...interface{}) { 141 | if ce := l.logger.Check(zap.PanicLevel, fmt.Sprint(msg...)); ce != nil { 142 | ce.Write() 143 | } 144 | } 145 | 146 | // Panicf formats a message according to a format specifier and logs the 147 | // message at the Panic level and then panics 148 | func (l Logger) Panicf(template string, args ...interface{}) { 149 | if ce := l.logger.Check(zap.PanicLevel, fmt.Sprintf(template, args...)); ce != nil { 150 | ce.Write() 151 | } 152 | } 153 | 154 | // Panicw logs a message at the Panic level along with some additional 155 | // context (key-value pairs) and then panics 156 | func (l Logger) Panicw(msg string, fields log.Fields) { 157 | if ce := l.logger.Check(zap.PanicLevel, msg); ce != nil { 158 | ce.Write(fieldToAny(fields)...) 159 | } 160 | } 161 | 162 | // Fatal logs a message at the Fatal level and exists the application 163 | func (l Logger) Fatal(msg ...interface{}) { 164 | if ce := l.logger.Check(zap.FatalLevel, fmt.Sprint(msg...)); ce != nil { 165 | ce.Write() 166 | } 167 | } 168 | 169 | // Fatalf formats a message according to a format specifier and logs the 170 | // message at the Fatal level and exits the application 171 | func (l Logger) Fatalf(template string, args ...interface{}) { 172 | if ce := l.logger.Check(zap.FatalLevel, fmt.Sprintf(template, args...)); ce != nil { 173 | ce.Write() 174 | } 175 | } 176 | 177 | // Fatalw logs a message at the Fatal level along with some additional 178 | // context (key-value pairs) and exits the application 179 | func (l Logger) Fatalw(msg string, fields log.Fields) { 180 | if ce := l.logger.Check(zap.FatalLevel, msg); ce != nil { 181 | ce.Write(fieldToAny(fields)...) 182 | } 183 | } 184 | 185 | func fieldToAny(flds log.Fields) []zapcore.Field { 186 | var ret []zapcore.Field 187 | for k, v := range flds { 188 | ret = append(ret, zap.Any(k, v)) 189 | } 190 | 191 | return ret 192 | } 193 | -------------------------------------------------------------------------------- /impl/zap/zap_test.go: -------------------------------------------------------------------------------- 1 | package zap 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/Masterminds/log-go" 9 | "github.com/stretchr/testify/assert" 10 | "go.uber.org/zap" 11 | "go.uber.org/zap/zaptest" 12 | ) 13 | 14 | func TestLogrus(t *testing.T) { 15 | 16 | // Test the logger meets the interface 17 | var _ log.Logger = new(Logger) 18 | 19 | ts := newTestLogSpy(t) 20 | defer ts.AssertPassed() 21 | logger := zaptest.NewLogger(ts, zaptest.Level(zap.DebugLevel-1)) 22 | defer func() { 23 | err := logger.Sync() 24 | if err != nil { 25 | t.Errorf("Error syncing logger: %s", err) 26 | } 27 | }() 28 | 29 | lgr := New(logger) 30 | 31 | lgr.Trace("test trace") 32 | lgr.Tracef("Hello %s", "World") 33 | lgr.Tracew("foo bar", log.Fields{"baz": "qux"}) 34 | lgr.Debug("test debug") 35 | lgr.Debugf("Hello %s", "World") 36 | lgr.Debugw("foo bar", log.Fields{"baz": "qux"}) 37 | lgr.Info("test info") 38 | lgr.Infof("Hello %s", "World") 39 | lgr.Infow("foo bar", log.Fields{"baz": "qux"}) 40 | lgr.Warn("test warn") 41 | lgr.Warnf("Hello %s", "World") 42 | lgr.Warnw("foo bar", log.Fields{"baz": "qux"}) 43 | lgr.Error("test error") 44 | lgr.Errorf("Hello %s", "World") 45 | lgr.Errorw("foo bar", log.Fields{"baz": "qux"}) 46 | assert.Panics(t, func() { 47 | lgr.Panic("test panic") 48 | }) 49 | assert.Panics(t, func() { 50 | lgr.Panicf("Hello %s", "World") 51 | }) 52 | assert.Panics(t, func() { 53 | lgr.Panicw("foo bar", log.Fields{"baz": "qux"}) 54 | }) 55 | 56 | ts.AssertMessages( 57 | "LEVEL(-2) test trace", 58 | "LEVEL(-2) Hello World", 59 | "LEVEL(-2) foo bar {\"baz\": \"qux\"}", 60 | "DEBUG test debug", 61 | "DEBUG Hello World", 62 | "DEBUG foo bar {\"baz\": \"qux\"}", 63 | "INFO test info", 64 | "INFO Hello World", 65 | "INFO foo bar {\"baz\": \"qux\"}", 66 | "WARN test warn", 67 | "WARN Hello World", 68 | "WARN foo bar {\"baz\": \"qux\"}", 69 | "ERROR test error", 70 | "ERROR Hello World", 71 | "ERROR foo bar {\"baz\": \"qux\"}", 72 | "PANIC test panic", 73 | "PANIC Hello World", 74 | "PANIC foo bar {\"baz\": \"qux\"}", 75 | ) 76 | 77 | // lgr.Fatal("test fatal") 78 | // lgr.Fatalf(template string, args ...interface{}) 79 | // lgr.Fatalw(msg string, fields Fields) 80 | 81 | } 82 | 83 | func TestZapInterface(t *testing.T) { 84 | logger := zaptest.NewLogger(t, zaptest.Level(zap.DebugLevel)) 85 | defer func() { 86 | err := logger.Sync() 87 | if err != nil { 88 | t.Errorf("Error syncing logger: %s", err) 89 | } 90 | }() 91 | lgr := New(logger) 92 | testfunc(lgr) 93 | } 94 | 95 | func testfunc(l log.Logger) { 96 | l.Debug("test") 97 | } 98 | 99 | // This last section is taken from zap itself and licensed under the MIT license 100 | type testLogSpy struct { 101 | testing.TB 102 | 103 | failed bool 104 | Messages []string 105 | } 106 | 107 | func newTestLogSpy(t testing.TB) *testLogSpy { 108 | return &testLogSpy{TB: t} 109 | } 110 | 111 | func (t *testLogSpy) Fail() { 112 | t.failed = true 113 | } 114 | 115 | func (t *testLogSpy) Failed() bool { 116 | return t.failed 117 | } 118 | 119 | func (t *testLogSpy) FailNow() { 120 | t.Fail() 121 | t.TB.FailNow() 122 | } 123 | 124 | func (t *testLogSpy) Logf(format string, args ...interface{}) { 125 | // Log messages are in the format, 126 | // 127 | // 2017-10-27T13:03:01.000-0700 DEBUG your message here {data here} 128 | // 129 | // We strip the first part of these messages because we can't really test 130 | // for the timestamp from these tests. 131 | m := fmt.Sprintf(format, args...) 132 | m = m[strings.IndexByte(m, '\t')+1:] 133 | t.Messages = append(t.Messages, m) 134 | t.TB.Log(m) 135 | } 136 | 137 | func (t *testLogSpy) AssertMessages(msgs ...string) { 138 | assert.Equal(t.TB, msgs, t.Messages, "logged messages did not match") 139 | } 140 | 141 | func (t *testLogSpy) AssertPassed() { 142 | t.assertFailed(false, "expected test to pass") 143 | } 144 | 145 | func (t *testLogSpy) AssertFailed() { 146 | t.assertFailed(true, "expected test to fail") 147 | } 148 | 149 | func (t *testLogSpy) assertFailed(v bool, msg string) { 150 | assert.Equal(t.TB, v, t.failed, msg) 151 | } 152 | -------------------------------------------------------------------------------- /io/io.go: -------------------------------------------------------------------------------- 1 | // Package io provides a means of turning a log.Logger into an io.Writer for 2 | // a chosen level. An example of this would be: 3 | // 4 | // import( 5 | // "io" 6 | // "github.com/Masterminds/log-go" 7 | // logio "github.com/Masterminds/log-go/io" 8 | // ) 9 | // 10 | // func main() { 11 | // w := logio.NewCurrentWriter(log.InfoLevel) 12 | // io.WriteString(w, "foo") 13 | // } 14 | package io 15 | 16 | import ( 17 | "github.com/Masterminds/log-go" 18 | ) 19 | 20 | // CurrentWriter uses the current package level logger for io writing 21 | type CurrentWriter struct { 22 | Level int 23 | } 24 | 25 | // NewCurrentWriter creates a new CurrentWriter. The levels that can be passed 26 | // to it are: 27 | // - log.TraceLevel: 28 | // - log.DebugLevel: 29 | // - log.InfoLevel: 30 | // - log.WarnLevel: 31 | // - log.ErrorLevel: 32 | // - log.PanicLevel: 33 | // - log.FatalLevel: 34 | func NewCurrentWriter(level int) *CurrentWriter { 35 | return &CurrentWriter{ 36 | Level: level, 37 | } 38 | } 39 | 40 | // Write is the write method from the io.Writer interface in the standard lib 41 | func (l CurrentWriter) Write(p []byte) (n int, err error) { 42 | switch l.Level { 43 | case log.TraceLevel: 44 | log.Trace(string(p)) 45 | case log.DebugLevel: 46 | log.Debug(string(p)) 47 | case log.InfoLevel: 48 | log.Info(string(p)) 49 | case log.WarnLevel: 50 | log.Warn(string(p)) 51 | case log.ErrorLevel: 52 | log.Error(string(p)) 53 | case log.PanicLevel: 54 | log.Panic(string(p)) 55 | case log.FatalLevel: 56 | log.Fatal(string(p)) 57 | default: 58 | log.Panicf("Invalid logger level selected: %d", l.Level) 59 | } 60 | 61 | return len(p), nil 62 | } 63 | 64 | // Writer uses the configured logger for io writing 65 | type Writer struct { 66 | Logger log.Logger 67 | Level int 68 | } 69 | 70 | // NewWriter creates a new Writer. It accepts a logger and a level that 71 | // will be written on the io.Writer interface. The levels you can pass in are: 72 | // - log.TraceLevel: 73 | // - log.DebugLevel: 74 | // - log.InfoLevel: 75 | // - log.WarnLevel: 76 | // - log.ErrorLevel: 77 | // - log.PanicLevel: 78 | // - log.FatalLevel: 79 | func NewWriter(lgr log.Logger, level int) *Writer { 80 | return &Writer{ 81 | Logger: lgr, 82 | Level: level, 83 | } 84 | } 85 | 86 | // Write is the write method from the io.Writer interface in the standard lib 87 | func (l Writer) Write(p []byte) (n int, err error) { 88 | switch l.Level { 89 | case log.TraceLevel: 90 | l.Logger.Trace(string(p)) 91 | case log.DebugLevel: 92 | l.Logger.Debug(string(p)) 93 | case log.InfoLevel: 94 | l.Logger.Info(string(p)) 95 | case log.WarnLevel: 96 | l.Logger.Warn(string(p)) 97 | case log.ErrorLevel: 98 | l.Logger.Error(string(p)) 99 | case log.PanicLevel: 100 | l.Logger.Panic(string(p)) 101 | case log.FatalLevel: 102 | l.Logger.Fatal(string(p)) 103 | default: 104 | l.Logger.Panicf("Invalid logger level selected: %d", l.Level) 105 | } 106 | 107 | return len(p), nil 108 | } 109 | -------------------------------------------------------------------------------- /io/io_test.go: -------------------------------------------------------------------------------- 1 | package io 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "strings" 8 | "testing" 9 | 10 | "github.com/Masterminds/log-go" 11 | ) 12 | 13 | func TestCurrentIO(t *testing.T) { 14 | buf := &bytes.Buffer{} 15 | lgr := newTestLogger(buf) 16 | def := log.Current 17 | log.Current = lgr 18 | defer func() { 19 | log.Current = def 20 | }() 21 | 22 | o := NewCurrentWriter(log.TraceLevel) 23 | _, _ = io.WriteString(o, "testing") 24 | if !strings.Contains(buf.String(), `level=trace msg="testing"`) { 25 | t.Error("current trace not logging correctly") 26 | } 27 | buf.Reset() 28 | 29 | o = NewCurrentWriter(log.DebugLevel) 30 | _, _ = io.WriteString(o, "testing") 31 | if !strings.Contains(buf.String(), `level=debug msg="testing"`) { 32 | t.Error("current debug not logging correctly") 33 | } 34 | buf.Reset() 35 | 36 | o = NewCurrentWriter(log.InfoLevel) 37 | _, _ = io.WriteString(o, "testing") 38 | if !strings.Contains(buf.String(), `level=info msg="testing"`) { 39 | t.Error("current info not logging correctly") 40 | } 41 | buf.Reset() 42 | 43 | o = NewCurrentWriter(log.WarnLevel) 44 | _, _ = io.WriteString(o, "testing") 45 | if !strings.Contains(buf.String(), `level=warning msg="testing"`) { 46 | t.Error("current warn not logging correctly") 47 | } 48 | buf.Reset() 49 | 50 | o = NewCurrentWriter(log.ErrorLevel) 51 | _, _ = io.WriteString(o, "testing") 52 | if !strings.Contains(buf.String(), `level=error msg="testing"`) { 53 | t.Error("current error not logging correctly") 54 | } 55 | buf.Reset() 56 | 57 | o = NewCurrentWriter(log.PanicLevel) 58 | _, _ = io.WriteString(o, "testing") 59 | if !strings.Contains(buf.String(), `level=panic msg="testing"`) { 60 | t.Error("current panic not logging correctly") 61 | } 62 | buf.Reset() 63 | 64 | o = NewCurrentWriter(log.FatalLevel) 65 | _, _ = io.WriteString(o, "testing") 66 | if !strings.Contains(buf.String(), `level=fatal msg="testing"`) { 67 | t.Error("current fatal not logging correctly") 68 | } 69 | buf.Reset() 70 | 71 | // Testing a non-existent level 72 | defer func() { 73 | if r := recover(); r != nil { 74 | t.Log(r) 75 | } 76 | }() 77 | log.Current = def // Test logger does not panic. Using one that does 78 | o = NewCurrentWriter(5000) 79 | _, _ = io.WriteString(o, "not happening") 80 | if !strings.Contains(buf.String(), `level=000 msg="testing"`) { 81 | t.Error("current fatal not logging correctly") 82 | } 83 | buf.Reset() 84 | t.Error("Not happening test failed to panic") 85 | } 86 | 87 | func TestIO(t *testing.T) { 88 | buf := &bytes.Buffer{} 89 | lgr := newTestLogger(buf) 90 | 91 | o := NewWriter(lgr, log.TraceLevel) 92 | _, _ = io.WriteString(o, "testing") 93 | if !strings.Contains(buf.String(), `level=trace msg="testing"`) { 94 | t.Error("current trace not logging correctly") 95 | } 96 | buf.Reset() 97 | 98 | o = NewWriter(lgr, log.DebugLevel) 99 | _, _ = io.WriteString(o, "testing") 100 | if !strings.Contains(buf.String(), `level=debug msg="testing"`) { 101 | t.Error("current debug not logging correctly") 102 | } 103 | buf.Reset() 104 | 105 | o = NewWriter(lgr, log.InfoLevel) 106 | _, _ = io.WriteString(o, "testing") 107 | if !strings.Contains(buf.String(), `level=info msg="testing"`) { 108 | t.Error("current info not logging correctly") 109 | } 110 | buf.Reset() 111 | 112 | o = NewWriter(lgr, log.WarnLevel) 113 | _, _ = io.WriteString(o, "testing") 114 | if !strings.Contains(buf.String(), `level=warning msg="testing"`) { 115 | t.Error("current warn not logging correctly") 116 | } 117 | buf.Reset() 118 | 119 | o = NewWriter(lgr, log.ErrorLevel) 120 | _, _ = io.WriteString(o, "testing") 121 | if !strings.Contains(buf.String(), `level=error msg="testing"`) { 122 | t.Error("current error not logging correctly") 123 | } 124 | buf.Reset() 125 | 126 | o = NewWriter(lgr, log.PanicLevel) 127 | _, _ = io.WriteString(o, "testing") 128 | if !strings.Contains(buf.String(), `level=panic msg="testing"`) { 129 | t.Error("current panic not logging correctly") 130 | } 131 | buf.Reset() 132 | 133 | o = NewWriter(lgr, log.FatalLevel) 134 | _, _ = io.WriteString(o, "testing") 135 | if !strings.Contains(buf.String(), `level=fatal msg="testing"`) { 136 | t.Error("current fatal not logging correctly") 137 | } 138 | buf.Reset() 139 | 140 | // Testing a non-existent level 141 | defer func() { 142 | if r := recover(); r != nil { 143 | t.Log(r) 144 | } 145 | }() 146 | o = NewWriter(log.Current, 5000) // Test logger does not panic. Using one that does 147 | _, _ = io.WriteString(o, "not happening") 148 | if !strings.Contains(buf.String(), `level=000 msg="testing"`) { 149 | t.Error("current fatal not logging correctly") 150 | } 151 | buf.Reset() 152 | t.Error("Not happening test failed to panic") 153 | } 154 | 155 | type testLogger struct { 156 | logger *bytes.Buffer 157 | } 158 | 159 | func newTestLogger(lgr *bytes.Buffer) *testLogger { 160 | return &testLogger{ 161 | logger: lgr, 162 | } 163 | } 164 | 165 | func (l testLogger) Trace(msg ...interface{}) { 166 | dummy(l.logger, "trace", msg...) 167 | } 168 | 169 | func (l testLogger) Tracef(template string, args ...interface{}) { 170 | dummyf(l.logger, "trace", template, args...) 171 | } 172 | 173 | func (l testLogger) Tracew(msg string, fields log.Fields) { 174 | dummyw(l.logger, "trace", msg, fields) 175 | } 176 | 177 | func (l testLogger) Debug(msg ...interface{}) { 178 | dummy(l.logger, "debug", msg...) 179 | } 180 | 181 | func (l testLogger) Debugf(template string, args ...interface{}) { 182 | dummyf(l.logger, "debug", template, args...) 183 | } 184 | 185 | func (l testLogger) Debugw(msg string, fields log.Fields) { 186 | dummyw(l.logger, "debug", msg, fields) 187 | } 188 | 189 | func (l testLogger) Info(msg ...interface{}) { 190 | dummy(l.logger, "info", msg...) 191 | } 192 | 193 | func (l testLogger) Infof(template string, args ...interface{}) { 194 | dummyf(l.logger, "info", template, args...) 195 | } 196 | 197 | func (l testLogger) Infow(msg string, fields log.Fields) { 198 | dummyw(l.logger, "info", msg, fields) 199 | } 200 | 201 | func (l testLogger) Warn(msg ...interface{}) { 202 | dummy(l.logger, "warning", msg...) 203 | } 204 | 205 | func (l testLogger) Warnf(template string, args ...interface{}) { 206 | dummyf(l.logger, "warning", template, args...) 207 | } 208 | 209 | func (l testLogger) Warnw(msg string, fields log.Fields) { 210 | dummyw(l.logger, "warning", msg, fields) 211 | } 212 | 213 | func (l testLogger) Error(msg ...interface{}) { 214 | dummy(l.logger, "error", msg...) 215 | } 216 | 217 | func (l testLogger) Errorf(template string, args ...interface{}) { 218 | dummyf(l.logger, "error", template, args...) 219 | } 220 | 221 | func (l testLogger) Errorw(msg string, fields log.Fields) { 222 | dummyw(l.logger, "error", msg, fields) 223 | } 224 | 225 | func (l testLogger) Panic(msg ...interface{}) { 226 | dummy(l.logger, "panic", msg...) 227 | } 228 | 229 | func (l testLogger) Panicf(template string, args ...interface{}) { 230 | dummyf(l.logger, "panic", template, args...) 231 | } 232 | 233 | func (l testLogger) Panicw(msg string, fields log.Fields) { 234 | dummyw(l.logger, "panic", msg, fields) 235 | } 236 | 237 | func (l testLogger) Fatal(msg ...interface{}) { 238 | dummy(l.logger, "fatal", msg...) 239 | } 240 | 241 | func (l testLogger) Fatalf(template string, args ...interface{}) { 242 | dummyf(l.logger, "fatal", template, args...) 243 | } 244 | 245 | func (l testLogger) Fatalw(msg string, fields log.Fields) { 246 | dummyw(l.logger, "fatal", msg, fields) 247 | } 248 | 249 | func dummy(buf *bytes.Buffer, level string, msg ...interface{}) { 250 | str := fmt.Sprintf(`level=%s msg="%s"`, level, fmt.Sprint(msg...)) 251 | buf.WriteString(str) 252 | } 253 | 254 | func dummyf(buf *bytes.Buffer, level, template string, args ...interface{}) { 255 | str := fmt.Sprintf(template, args...) 256 | str = fmt.Sprintf(`level=%s msg="%s"`, level, str) 257 | buf.WriteString(str) 258 | } 259 | 260 | func dummyw(buf *bytes.Buffer, level string, msg interface{}, fields log.Fields) { 261 | var flds string 262 | for k, v := range fields { 263 | flds += fmt.Sprintf("%s=%s ", k, v) 264 | } 265 | str := fmt.Sprintf(`level=%s msg="%s" %s`, level, msg, flds) 266 | buf.WriteString(str) 267 | } 268 | -------------------------------------------------------------------------------- /log.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | // Fields represents a map of key-value pairs where the value can be any Go 4 | // type. The value must be able to be converted to a string. 5 | type Fields map[string]interface{} 6 | 7 | // Logger is an interface for Logging 8 | type Logger interface { 9 | // Trace logs a message at the Trace level 10 | Trace(msg ...interface{}) 11 | 12 | // Tracef formats a message according to a format specifier and logs the 13 | // message at the Trace level 14 | Tracef(template string, args ...interface{}) 15 | 16 | // Tracew logs a message at the Trace level along with some additional 17 | // context (key-value pairs) 18 | Tracew(msg string, fields Fields) 19 | 20 | // Debug logs a message at the Debug level 21 | Debug(msg ...interface{}) 22 | 23 | // Debugf formats a message according to a format specifier and logs the 24 | // message at the Debug level 25 | Debugf(template string, args ...interface{}) 26 | 27 | // Debugw logs a message at the Debug level along with some additional 28 | // context (key-value pairs) 29 | Debugw(msg string, fields Fields) 30 | 31 | // Info logs a message at the Info level 32 | Info(msg ...interface{}) 33 | 34 | // Infof formats a message according to a format specifier and logs the 35 | // message at the Info level 36 | Infof(template string, args ...interface{}) 37 | 38 | // Infow logs a message at the Info level along with some additional 39 | // context (key-value pairs) 40 | Infow(msg string, fields Fields) 41 | 42 | // Warn logs a message at the Warn level 43 | Warn(msg ...interface{}) 44 | 45 | // Warnf formats a message according to a format specifier and logs the 46 | // message at the Warning level 47 | Warnf(template string, args ...interface{}) 48 | 49 | // Warnw logs a message at the Warning level along with some additional 50 | // context (key-value pairs) 51 | Warnw(msg string, fields Fields) 52 | 53 | // Error logs a message at the Error level 54 | Error(msg ...interface{}) 55 | 56 | // Errorf formats a message according to a format specifier and logs the 57 | // message at the Error level 58 | Errorf(template string, args ...interface{}) 59 | 60 | // Errorw logs a message at the Error level along with some additional 61 | // context (key-value pairs) 62 | Errorw(msg string, fields Fields) 63 | 64 | // Panic logs a message at the Panic level and panics 65 | Panic(msg ...interface{}) 66 | 67 | // Panicf formats a message according to a format specifier and logs the 68 | // message at the Panic level and then panics 69 | Panicf(template string, args ...interface{}) 70 | 71 | // Panicw logs a message at the Panic level along with some additional 72 | // context (key-value pairs) and then panics 73 | Panicw(msg string, fields Fields) 74 | 75 | // Fatal logs a message at the Fatal level and exists the application 76 | Fatal(msg ...interface{}) 77 | 78 | // Fatalf formats a message according to a format specifier and logs the 79 | // message at the Fatal level and exits the application 80 | Fatalf(template string, args ...interface{}) 81 | 82 | // Fatalw logs a message at the Fatal level along with some additional 83 | // context (key-value pairs) and exits the application 84 | Fatalw(msg string, fields Fields) 85 | } 86 | 87 | // Current contains the logger used for the package level logging functions 88 | var Current Logger 89 | 90 | func init() { 91 | Current = NewStandard() 92 | } 93 | 94 | // Trace logs a message at the Trace level 95 | func Trace(msg ...interface{}) { 96 | Current.Trace(msg...) 97 | } 98 | 99 | // Tracef formats a message according to a format specifier and logs the 100 | // message at the Trace level 101 | func Tracef(template string, args ...interface{}) { 102 | Current.Tracef(template, args...) 103 | } 104 | 105 | // Tracew logs a message at the Trace level along with some additional 106 | // context (key-value pairs) 107 | func Tracew(msg string, fields Fields) { 108 | Current.Tracew(msg, fields) 109 | } 110 | 111 | // Debug logs a message at the Debug level 112 | func Debug(msg ...interface{}) { 113 | Current.Debug(msg...) 114 | } 115 | 116 | // Debugf formats a message according to a format specifier and logs the 117 | // message at the Debug level 118 | func Debugf(template string, args ...interface{}) { 119 | Current.Debugf(template, args...) 120 | } 121 | 122 | // Debugw logs a message at the Debug level along with some additional 123 | // context (key-value pairs) 124 | func Debugw(msg string, fields Fields) { 125 | Current.Debugw(msg, fields) 126 | } 127 | 128 | // Info logs a message at the Info level 129 | func Info(msg ...interface{}) { 130 | Current.Info(msg...) 131 | } 132 | 133 | // Infof formats a message according to a format specifier and logs the 134 | // message at the Info level 135 | func Infof(template string, args ...interface{}) { 136 | Current.Infof(template, args...) 137 | } 138 | 139 | // Infow logs a message at the Info level along with some additional 140 | // context (key-value pairs) 141 | func Infow(msg string, fields Fields) { 142 | Current.Infow(msg, fields) 143 | } 144 | 145 | // Warn logs a message at the Warn level 146 | func Warn(msg ...interface{}) { 147 | Current.Warn(msg...) 148 | } 149 | 150 | // Warnf formats a message according to a format specifier and logs the 151 | // message at the Warning level 152 | func Warnf(template string, args ...interface{}) { 153 | Current.Warnf(template, args...) 154 | } 155 | 156 | // Warnw logs a message at the Warning level along with some additional 157 | // context (key-value pairs) 158 | func Warnw(msg string, fields Fields) { 159 | Current.Warnw(msg, fields) 160 | } 161 | 162 | // Error logs a message at the Error level 163 | func Error(msg ...interface{}) { 164 | Current.Error(msg...) 165 | } 166 | 167 | // Errorf formats a message according to a format specifier and logs the 168 | // message at the Error level 169 | func Errorf(template string, args ...interface{}) { 170 | Current.Errorf(template, args...) 171 | } 172 | 173 | // Errorw logs a message at the Error level along with some additional 174 | // context (key-value pairs) 175 | func Errorw(msg string, fields Fields) { 176 | Current.Errorw(msg, fields) 177 | } 178 | 179 | // Panic logs a message at the Panic level and panics 180 | func Panic(msg ...interface{}) { 181 | Current.Panic(msg...) 182 | } 183 | 184 | // Panicf formats a message according to a format specifier and logs the 185 | // message at the Panic level and then panics 186 | func Panicf(template string, args ...interface{}) { 187 | Current.Panicf(template, args...) 188 | } 189 | 190 | // Panicw logs a message at the Panic level along with some additional 191 | // context (key-value pairs) and then panics 192 | func Panicw(msg string, fields Fields) { 193 | Current.Panicw(msg, fields) 194 | } 195 | 196 | // Fatal logs a message at the Fatal level and exists the application 197 | func Fatal(msg ...interface{}) { 198 | Current.Fatal(msg...) 199 | } 200 | 201 | // Fatalf formats a message according to a format specifier and logs the 202 | // message at the Fatal level and exits the application 203 | func Fatalf(template string, args ...interface{}) { 204 | Current.Fatalf(template, args...) 205 | } 206 | 207 | // Fatalw logs a message at the Fatal level along with some additional 208 | // context (key-value pairs) and exits the application 209 | func Fatalw(msg string, fields Fields) { 210 | Current.Fatalw(msg, fields) 211 | } 212 | -------------------------------------------------------------------------------- /log_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "strings" 7 | "testing" 8 | ) 9 | 10 | func TestInterface(t *testing.T) { 11 | buf := &bytes.Buffer{} 12 | lgr := newTestLogger(buf) 13 | Current = lgr 14 | 15 | Trace("test trace") 16 | if !strings.Contains(buf.String(), `level=trace msg="test trace"`) { 17 | t.Error("current trace not logging correctly") 18 | } 19 | buf.Reset() 20 | 21 | Tracef("Hello %s", "World") 22 | if !strings.Contains(buf.String(), `level=trace msg="Hello World"`) { 23 | t.Error("current trace not logging correctly") 24 | } 25 | buf.Reset() 26 | 27 | Tracew("foo bar", Fields{"baz": "qux"}) 28 | if !strings.Contains(buf.String(), `level=trace msg="foo bar" baz=qux`) { 29 | t.Log(buf.String()) 30 | t.Error("current trace not logging correctly") 31 | } 32 | buf.Reset() 33 | 34 | Debug("test debug") 35 | if !strings.Contains(buf.String(), `level=debug msg="test debug"`) { 36 | t.Error("current debug not logging correctly") 37 | } 38 | buf.Reset() 39 | 40 | Debugf("Hello %s", "World") 41 | if !strings.Contains(buf.String(), `level=debug msg="Hello World"`) { 42 | t.Error("current debug not logging correctly") 43 | } 44 | buf.Reset() 45 | 46 | Debugw("foo bar", Fields{"baz": "qux"}) 47 | if !strings.Contains(buf.String(), `level=debug msg="foo bar" baz=qux`) { 48 | t.Log(buf.String()) 49 | t.Error("current info not logging correctly") 50 | } 51 | buf.Reset() 52 | 53 | Info("test info") 54 | if !strings.Contains(buf.String(), `level=info msg="test info"`) { 55 | t.Error("current info not logging correctly") 56 | } 57 | buf.Reset() 58 | 59 | Infof("Hello %s", "World") 60 | if !strings.Contains(buf.String(), `level=info msg="Hello World"`) { 61 | t.Error("current info not logging correctly") 62 | } 63 | buf.Reset() 64 | 65 | Infow("foo bar", Fields{"baz": "qux"}) 66 | if !strings.Contains(buf.String(), `level=info msg="foo bar" baz=qux`) { 67 | t.Log(buf.String()) 68 | t.Error("current info not logging correctly") 69 | } 70 | buf.Reset() 71 | 72 | Warn("test warn") 73 | if !strings.Contains(buf.String(), `level=warning msg="test warn"`) { 74 | t.Log(buf.String()) 75 | t.Error("current warn not logging correctly") 76 | } 77 | buf.Reset() 78 | 79 | Warnf("Hello %s", "World") 80 | if !strings.Contains(buf.String(), `level=warning msg="Hello World"`) { 81 | t.Log(buf.String()) 82 | t.Error("current warn not logging correctly") 83 | } 84 | buf.Reset() 85 | 86 | Warnw("foo bar", Fields{"baz": "qux"}) 87 | if !strings.Contains(buf.String(), `level=warning msg="foo bar" baz=qux`) { 88 | t.Log(buf.String()) 89 | t.Error("current warn not logging correctly") 90 | } 91 | buf.Reset() 92 | 93 | Error("test error") 94 | if !strings.Contains(buf.String(), `level=error msg="test error"`) { 95 | t.Log(buf.String()) 96 | t.Error("current error not logging correctly") 97 | } 98 | buf.Reset() 99 | 100 | Errorf("Hello %s", "World") 101 | if !strings.Contains(buf.String(), `level=error msg="Hello World"`) { 102 | t.Log(buf.String()) 103 | t.Error("current error not logging correctly") 104 | } 105 | buf.Reset() 106 | 107 | Errorw("foo bar", Fields{"baz": "qux"}) 108 | if !strings.Contains(buf.String(), `level=error msg="foo bar" baz=qux`) { 109 | t.Log(buf.String()) 110 | t.Error("current error not logging correctly") 111 | } 112 | buf.Reset() 113 | 114 | Panic("test panic") 115 | if !strings.Contains(buf.String(), `level=panic msg="test panic"`) { 116 | t.Log(buf.String()) 117 | t.Error("current panic not logging correctly") 118 | } 119 | buf.Reset() 120 | 121 | Panicf("Hello %s", "World") 122 | if !strings.Contains(buf.String(), `level=panic msg="Hello World"`) { 123 | t.Log(buf.String()) 124 | t.Error("current panic not logging correctly") 125 | } 126 | buf.Reset() 127 | 128 | Panicw("foo bar", Fields{"baz": "qux"}) 129 | if !strings.Contains(buf.String(), `level=panic msg="foo bar" baz=qux`) { 130 | t.Log(buf.String()) 131 | t.Error("current panic not logging correctly") 132 | } 133 | buf.Reset() 134 | 135 | Fatal("test fatal") 136 | if !strings.Contains(buf.String(), `level=fatal msg="test fatal"`) { 137 | t.Log(buf.String()) 138 | t.Error("current error not logging correctly") 139 | } 140 | buf.Reset() 141 | 142 | Fatalf("Hello %s", "World") 143 | if !strings.Contains(buf.String(), `level=fatal msg="Hello World"`) { 144 | t.Log(buf.String()) 145 | t.Error("current error not logging correctly") 146 | } 147 | buf.Reset() 148 | 149 | Fatalw("foo bar", Fields{"baz": "qux"}) 150 | if !strings.Contains(buf.String(), `level=fatal msg="foo bar" baz=qux`) { 151 | t.Log(buf.String()) 152 | t.Error("current error not logging correctly") 153 | } 154 | buf.Reset() 155 | } 156 | 157 | type testLogger struct { 158 | logger *bytes.Buffer 159 | } 160 | 161 | func newTestLogger(lgr *bytes.Buffer) *testLogger { 162 | return &testLogger{ 163 | logger: lgr, 164 | } 165 | } 166 | 167 | func (l testLogger) Trace(msg ...interface{}) { 168 | dummy(l.logger, "trace", msg...) 169 | } 170 | 171 | func (l testLogger) Tracef(template string, args ...interface{}) { 172 | dummyf(l.logger, "trace", template, args...) 173 | } 174 | 175 | func (l testLogger) Tracew(msg string, fields Fields) { 176 | dummyw(l.logger, "trace", msg, fields) 177 | } 178 | 179 | func (l testLogger) Debug(msg ...interface{}) { 180 | dummy(l.logger, "debug", msg...) 181 | } 182 | 183 | func (l testLogger) Debugf(template string, args ...interface{}) { 184 | dummyf(l.logger, "debug", template, args...) 185 | } 186 | 187 | func (l testLogger) Debugw(msg string, fields Fields) { 188 | dummyw(l.logger, "debug", msg, fields) 189 | } 190 | 191 | func (l testLogger) Info(msg ...interface{}) { 192 | dummy(l.logger, "info", msg...) 193 | } 194 | 195 | func (l testLogger) Infof(template string, args ...interface{}) { 196 | dummyf(l.logger, "info", template, args...) 197 | } 198 | 199 | func (l testLogger) Infow(msg string, fields Fields) { 200 | dummyw(l.logger, "info", msg, fields) 201 | } 202 | 203 | func (l testLogger) Warn(msg ...interface{}) { 204 | dummy(l.logger, "warning", msg...) 205 | } 206 | 207 | func (l testLogger) Warnf(template string, args ...interface{}) { 208 | dummyf(l.logger, "warning", template, args...) 209 | } 210 | 211 | func (l testLogger) Warnw(msg string, fields Fields) { 212 | dummyw(l.logger, "warning", msg, fields) 213 | } 214 | 215 | func (l testLogger) Error(msg ...interface{}) { 216 | dummy(l.logger, "error", msg...) 217 | } 218 | 219 | func (l testLogger) Errorf(template string, args ...interface{}) { 220 | dummyf(l.logger, "error", template, args...) 221 | } 222 | 223 | func (l testLogger) Errorw(msg string, fields Fields) { 224 | dummyw(l.logger, "error", msg, fields) 225 | } 226 | 227 | func (l testLogger) Panic(msg ...interface{}) { 228 | dummy(l.logger, "panic", msg...) 229 | } 230 | 231 | func (l testLogger) Panicf(template string, args ...interface{}) { 232 | dummyf(l.logger, "panic", template, args...) 233 | } 234 | 235 | func (l testLogger) Panicw(msg string, fields Fields) { 236 | dummyw(l.logger, "panic", msg, fields) 237 | } 238 | 239 | func (l testLogger) Fatal(msg ...interface{}) { 240 | dummy(l.logger, "fatal", msg...) 241 | } 242 | 243 | func (l testLogger) Fatalf(template string, args ...interface{}) { 244 | dummyf(l.logger, "fatal", template, args...) 245 | } 246 | 247 | func (l testLogger) Fatalw(msg string, fields Fields) { 248 | dummyw(l.logger, "fatal", msg, fields) 249 | } 250 | 251 | func dummy(buf *bytes.Buffer, level string, msg ...interface{}) { 252 | str := fmt.Sprintf(`level=%s msg="%s"`, level, fmt.Sprint(msg...)) 253 | buf.WriteString(str) 254 | } 255 | 256 | func dummyf(buf *bytes.Buffer, level, template string, args ...interface{}) { 257 | str := fmt.Sprintf(template, args...) 258 | str = fmt.Sprintf(`level=%s msg="%s"`, level, str) 259 | buf.WriteString(str) 260 | } 261 | 262 | func dummyw(buf *bytes.Buffer, level string, msg interface{}, fields Fields) { 263 | var flds string 264 | for k, v := range fields { 265 | flds += fmt.Sprintf("%s=%s ", k, v) 266 | } 267 | str := fmt.Sprintf(`level=%s msg="%s" %s`, level, msg, flds) 268 | buf.WriteString(str) 269 | } 270 | -------------------------------------------------------------------------------- /std.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "fmt" 5 | stdlog "log" 6 | ) 7 | 8 | // NewStandard sets up a basic logger using the general one provided in the Go 9 | // standard library 10 | func NewStandard() *StdLogger { 11 | return &StdLogger{ 12 | Level: InfoLevel, 13 | } 14 | } 15 | 16 | const ( 17 | // TraceLevel is the constant to use when setting the Trace level for loggers 18 | // provided by this library. 19 | TraceLevel = iota 20 | 21 | // DebugLevel is the constant to use when setting the Debug level for loggers 22 | // provided by this library. 23 | DebugLevel 24 | 25 | // InfoLevel is the constant to use when setting the Info level for loggers 26 | // provided by this library. 27 | InfoLevel 28 | 29 | // WarnLevel is the constant to use when setting the Warn level for loggers 30 | // provided by this library. 31 | WarnLevel 32 | 33 | // ErrorLevel is the constant to use when setting the Error level for loggers 34 | // provided by this library. 35 | ErrorLevel 36 | 37 | // PanicLevel is the constant to use when setting the Panic level for loggers 38 | // provided by this library. 39 | PanicLevel 40 | 41 | // FatalLevel is the constant to use when setting the Fatal level for loggers 42 | // provided by this library. 43 | FatalLevel 44 | ) 45 | 46 | // StdLogger is a struct that wraps the general logger provided by the Go 47 | // standard library and causes it to meet the log.Logger interface 48 | type StdLogger struct { 49 | Level int 50 | } 51 | 52 | // Trace logs a message at the Trace level 53 | func (l StdLogger) Trace(msg ...interface{}) { 54 | if l.Level <= TraceLevel { 55 | stdlog.Print(append([]interface{}{"[TRACE] "}, msg...)...) 56 | } 57 | } 58 | 59 | // Tracef formats a message according to a format specifier and logs the 60 | // message at the Trace level 61 | func (l StdLogger) Tracef(template string, args ...interface{}) { 62 | if l.Level <= TraceLevel { 63 | stdlog.Printf("[TRACE] "+template, args...) 64 | } 65 | } 66 | 67 | // Tracew logs a message at the Trace level along with some additional 68 | // context (key-value pairs) 69 | func (l StdLogger) Tracew(msg string, fields Fields) { 70 | if l.Level <= TraceLevel { 71 | stdlog.Printf("[TRACE] %s %s", msg, handlFields(fields)) 72 | } 73 | } 74 | 75 | // Debug logs a message at the Debug level 76 | func (l StdLogger) Debug(msg ...interface{}) { 77 | if l.Level <= DebugLevel { 78 | stdlog.Print(append([]interface{}{"[DEBUG] "}, msg...)...) 79 | } 80 | } 81 | 82 | // Debugf formats a message according to a format specifier and logs the 83 | // message at the Debug level 84 | func (l StdLogger) Debugf(template string, args ...interface{}) { 85 | if l.Level <= DebugLevel { 86 | stdlog.Printf("[DEBUG] "+template, args...) 87 | } 88 | } 89 | 90 | // Debugw logs a message at the Debug level along with some additional 91 | // context (key-value pairs) 92 | func (l StdLogger) Debugw(msg string, fields Fields) { 93 | if l.Level <= DebugLevel { 94 | stdlog.Printf("[DEBUG] %s %s", msg, handlFields(fields)) 95 | } 96 | } 97 | 98 | // Info logs a message at the Info level 99 | func (l StdLogger) Info(msg ...interface{}) { 100 | if l.Level <= InfoLevel { 101 | stdlog.Print(append([]interface{}{"[INFO] "}, msg...)...) 102 | } 103 | } 104 | 105 | // Infof formats a message according to a format specifier and logs the 106 | // message at the Info level 107 | func (l StdLogger) Infof(template string, args ...interface{}) { 108 | if l.Level <= InfoLevel { 109 | stdlog.Printf("[INFO] "+template, args...) 110 | } 111 | } 112 | 113 | // Infow logs a message at the Info level along with some additional 114 | // context (key-value pairs) 115 | func (l StdLogger) Infow(msg string, fields Fields) { 116 | if l.Level <= InfoLevel { 117 | stdlog.Printf("[INFO] %s %s", msg, handlFields(fields)) 118 | } 119 | } 120 | 121 | // Warn logs a message at the Warn level 122 | func (l StdLogger) Warn(msg ...interface{}) { 123 | if l.Level <= WarnLevel { 124 | stdlog.Print(append([]interface{}{"[WARNING] "}, msg...)...) 125 | } 126 | } 127 | 128 | // Warnf formats a message according to a format specifier and logs the 129 | // message at the Warning level 130 | func (l StdLogger) Warnf(template string, args ...interface{}) { 131 | if l.Level <= WarnLevel { 132 | stdlog.Printf("[WARNING] "+template, args...) 133 | } 134 | } 135 | 136 | // Warnw logs a message at the Warning level along with some additional 137 | // context (key-value pairs) 138 | func (l StdLogger) Warnw(msg string, fields Fields) { 139 | if l.Level <= WarnLevel { 140 | stdlog.Printf("[WARNING] %s %s", msg, handlFields(fields)) 141 | } 142 | } 143 | 144 | // Error logs a message at the Error level 145 | func (l StdLogger) Error(msg ...interface{}) { 146 | if l.Level <= ErrorLevel { 147 | stdlog.Print(append([]interface{}{"[ERROR] "}, msg...)...) 148 | } 149 | } 150 | 151 | // Errorf formats a message according to a format specifier and logs the 152 | // message at the Error level 153 | func (l StdLogger) Errorf(template string, args ...interface{}) { 154 | if l.Level <= ErrorLevel { 155 | stdlog.Printf("[ERROR] "+template, args...) 156 | } 157 | } 158 | 159 | // Errorw logs a message at the Error level along with some additional 160 | // context (key-value pairs) 161 | func (l StdLogger) Errorw(msg string, fields Fields) { 162 | if l.Level <= ErrorLevel { 163 | stdlog.Printf("[ERROR] %s %s", msg, handlFields(fields)) 164 | } 165 | } 166 | 167 | // Panic logs a message at the Panic level and panics 168 | func (l StdLogger) Panic(msg ...interface{}) { 169 | if l.Level <= PanicLevel { 170 | stdlog.Panic(append([]interface{}{"[PANIC] "}, msg...)...) 171 | } 172 | } 173 | 174 | // Panicf formats a message according to a format specifier and logs the 175 | // message at the Panic level and then panics 176 | func (l StdLogger) Panicf(template string, args ...interface{}) { 177 | if l.Level <= PanicLevel { 178 | stdlog.Panicf("[PANIC] "+template, args...) 179 | } 180 | } 181 | 182 | // Panicw logs a message at the Panic level along with some additional 183 | // context (key-value pairs) and then panics 184 | func (l StdLogger) Panicw(msg string, fields Fields) { 185 | if l.Level <= PanicLevel { 186 | stdlog.Panicf("[PANIC] %s %s", msg, handlFields(fields)) 187 | } 188 | } 189 | 190 | // Fatal logs a message at the Fatal level and exists the application 191 | func (l StdLogger) Fatal(msg ...interface{}) { 192 | if l.Level <= FatalLevel { 193 | stdlog.Fatal(append([]interface{}{"[FATAL] "}, msg...)...) 194 | } 195 | } 196 | 197 | // Fatalf formats a message according to a format specifier and logs the 198 | // message at the Fatal level and exits the application 199 | func (l StdLogger) Fatalf(template string, args ...interface{}) { 200 | if l.Level <= FatalLevel { 201 | stdlog.Fatalf("[FATAL] "+template, args...) 202 | } 203 | } 204 | 205 | // Fatalw logs a message at the Fatal level along with some additional 206 | // context (key-value pairs) and exits the application 207 | func (l StdLogger) Fatalw(msg string, fields Fields) { 208 | if l.Level <= FatalLevel { 209 | stdlog.Fatalf("[FATAL] %s %s", msg, handlFields(fields)) 210 | } 211 | } 212 | 213 | func handlFields(flds Fields) string { 214 | var ret string 215 | for k, v := range flds { 216 | ret += fmt.Sprintf("[%s=%s]", k, v) 217 | } 218 | return ret 219 | } 220 | -------------------------------------------------------------------------------- /std_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "bytes" 5 | stdlog "log" 6 | "os" 7 | "strings" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestStdLogger(t *testing.T) { 14 | buf := &bytes.Buffer{} 15 | stdlog.SetOutput(buf) 16 | defer func() { 17 | stdlog.SetOutput(os.Stderr) 18 | }() 19 | 20 | lgr := NewStandard() 21 | 22 | // Make sure levels are working 23 | lgr.Debug("test debug") 24 | if strings.Contains(buf.String(), `[DEBUG] test debug`) { 25 | t.Log(buf.String()) 26 | t.Error("stdlib debug not logging correctly") 27 | } 28 | buf.Reset() 29 | 30 | // Test all levels 31 | lgr.Level = TraceLevel 32 | 33 | lgr.Trace("test debug") 34 | if !strings.Contains(buf.String(), `[TRACE] test debug`) { 35 | t.Log(buf.String()) 36 | t.Error("stdlib trace not logging correctly") 37 | } 38 | buf.Reset() 39 | 40 | lgr.Tracef("Hello %s", "World") 41 | if !strings.Contains(buf.String(), `[TRACE] Hello World`) { 42 | t.Error("stdlib trace not logging correctly") 43 | } 44 | buf.Reset() 45 | 46 | lgr.Tracew("foo bar", Fields{"baz": "qux"}) 47 | if !strings.Contains(buf.String(), `[TRACE] foo bar [baz=qux]`) { 48 | t.Log(buf.String()) 49 | t.Error("stdlib trace not logging correctly") 50 | } 51 | buf.Reset() 52 | 53 | lgr.Debug("test debug") 54 | if !strings.Contains(buf.String(), `[DEBUG] test debug`) { 55 | t.Log(buf.String()) 56 | t.Error("stdlib debug not logging correctly") 57 | } 58 | buf.Reset() 59 | 60 | lgr.Debugf("Hello %s", "World") 61 | if !strings.Contains(buf.String(), `[DEBUG] Hello World`) { 62 | t.Error("stdlib debug not logging correctly") 63 | } 64 | buf.Reset() 65 | 66 | lgr.Debugw("foo bar", Fields{"baz": "qux"}) 67 | if !strings.Contains(buf.String(), `[DEBUG] foo bar [baz=qux]`) { 68 | t.Log(buf.String()) 69 | t.Error("stdlib debug not logging correctly") 70 | } 71 | buf.Reset() 72 | 73 | lgr.Info("test info") 74 | if !strings.Contains(buf.String(), `[INFO] test info`) { 75 | t.Error("stdlib info not logging correctly") 76 | } 77 | buf.Reset() 78 | 79 | lgr.Infof("Hello %s", "World") 80 | if !strings.Contains(buf.String(), `[INFO] Hello World`) { 81 | t.Error("stdlib info not logging correctly") 82 | } 83 | buf.Reset() 84 | 85 | lgr.Infow("foo bar", Fields{"baz": "qux"}) 86 | if !strings.Contains(buf.String(), `[INFO] foo bar [baz=qux]`) { 87 | t.Log(buf.String()) 88 | t.Error("stdlib info not logging correctly") 89 | } 90 | buf.Reset() 91 | 92 | lgr.Warn("test warn") 93 | if !strings.Contains(buf.String(), `[WARNING] test warn`) { 94 | t.Log(buf.String()) 95 | t.Error("stdlib warn not logging correctly") 96 | } 97 | buf.Reset() 98 | 99 | lgr.Warnf("Hello %s", "World") 100 | if !strings.Contains(buf.String(), `[WARNING] Hello World`) { 101 | t.Log(buf.String()) 102 | t.Error("stdlib warn not logging correctly") 103 | } 104 | buf.Reset() 105 | 106 | lgr.Warnw("foo bar", Fields{"baz": "qux"}) 107 | if !strings.Contains(buf.String(), `[WARNING] foo bar [baz=qux]`) { 108 | t.Log(buf.String()) 109 | t.Error("stdlib warn not logging correctly") 110 | } 111 | buf.Reset() 112 | 113 | lgr.Error("test error") 114 | if !strings.Contains(buf.String(), `[ERROR] test error`) { 115 | t.Log(buf.String()) 116 | t.Error("stdlib error not logging correctly") 117 | } 118 | buf.Reset() 119 | 120 | lgr.Errorf("Hello %s", "World") 121 | if !strings.Contains(buf.String(), `[ERROR] Hello World`) { 122 | t.Log(buf.String()) 123 | t.Error("stdlib error not logging correctly") 124 | } 125 | buf.Reset() 126 | 127 | lgr.Errorw("foo bar", Fields{"baz": "qux"}) 128 | if !strings.Contains(buf.String(), `[ERROR] foo bar [baz=qux]`) { 129 | t.Log(buf.String()) 130 | t.Error("stdlib error not logging correctly") 131 | } 132 | buf.Reset() 133 | 134 | assert.PanicsWithValue(t, "[PANIC] test panic", func() { 135 | lgr.Panic("test panic") 136 | }) 137 | if !strings.Contains(buf.String(), `test panic`) { 138 | t.Log(buf.String()) 139 | t.Error("cli panic not logging correctly") 140 | } 141 | buf.Reset() 142 | 143 | assert.PanicsWithValue(t, "[PANIC] Hello World", func() { 144 | lgr.Panicf("Hello %s", "World") 145 | }) 146 | if !strings.Contains(buf.String(), `Hello World`) { 147 | t.Log(buf.String()) 148 | t.Error("cli panic not logging correctly") 149 | } 150 | buf.Reset() 151 | 152 | assert.PanicsWithValue(t, "[PANIC] foo bar [baz=qux]", func() { 153 | lgr.Panicw("foo bar", Fields{"baz": "qux"}) 154 | }) 155 | if !strings.Contains(buf.String(), `foo bar [baz=qux]`) { 156 | t.Log(buf.String()) 157 | t.Error("cli panic not logging correctly") 158 | } 159 | buf.Reset() 160 | 161 | // lgr.Fatal("test fatal") 162 | // lgr.Fatalf(template string, args ...interface{}) 163 | // lgr.Fatalw(msg string, fields Fields) 164 | 165 | } 166 | --------------------------------------------------------------------------------