Skip to content

Commit 523da6d

Browse files
Added logging package (#85)
* added logging package * updated code documentation for logging package * updated error handling to write operations --------- Co-authored-by: Kashif Khan <70996046+kashifkhan0771@users.noreply.github.com>
1 parent 68f57ef commit 523da6d

File tree

4 files changed

+438
-1
lines changed

4 files changed

+438
-1
lines changed

EXAMPLES.md

Lines changed: 143 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ This document provides practical examples of how to use the library's features.
1616
12. [Math](#12-math)
1717
13. [Fake](#13-fake)
1818
14. [Time](#14-time)
19+
14. [Loggin](#15-logging)
1920

2021
## 1. Boolean
2122

@@ -2271,4 +2272,145 @@ Is now today? true
22712272
Is tomorrow today? false
22722273
```
22732274

2274-
---
2275+
---
2276+
2277+
## 15. Logging
2278+
2279+
### Create and use a logger
2280+
```go
2281+
package main
2282+
2283+
import (
2284+
logging "github.com/kashifkhan0771/utils/logging"
2285+
"os"
2286+
)
2287+
2288+
func main() {
2289+
// Create a new logger with prefix "MyApp", minimum level INFO, and output to stdout
2290+
logger := logging.NewLogger("MyApp", logging.INFO, os.Stdout)
2291+
2292+
// Log messages of different levels
2293+
logger.Debug("This is a debug message.") // Ignored because minLevel is INFO
2294+
logger.Info("Application started.") // Printed with blue color
2295+
logger.Warn("Low disk space.") // Printed with yellow color
2296+
logger.Error("Failed to connect to DB.") // Printed with red color
2297+
}
2298+
```
2299+
#### Output:
2300+
```
2301+
[2025-01-09 12:34:56] [INFO] MyApp: Application started.
2302+
[2025-01-09 12:34:56] [WARN] MyApp: Low disk space.
2303+
[2025-01-09 12:34:56] [ERROR] MyApp: Failed to connect to DB.
2304+
```
2305+
2306+
### Log without colors (useful for plain text logs)
2307+
```go
2308+
package main
2309+
2310+
import (
2311+
logging "github.com/kashifkhan0771/utils/logging"
2312+
"os"
2313+
)
2314+
2315+
func main() {
2316+
// Create a logger and disable colors
2317+
logger := logging.NewLogger("MyApp", logging.DEBUG, os.Stdout)
2318+
logger.disableColors = true
2319+
2320+
// Log messages of different levels
2321+
logger.Debug("Debugging without colors.")
2322+
logger.Info("Information without colors.")
2323+
logger.Warn("Warning without colors.")
2324+
logger.Error("Error without colors.")
2325+
}
2326+
```
2327+
#### Output:
2328+
```
2329+
[2025-01-09 12:34:56] [DEBUG] MyApp: Debugging without colors.
2330+
[2025-01-09 12:34:56] [INFO] MyApp: Information without colors.
2331+
[2025-01-09 12:34:56] [WARN] MyApp: Warning without colors.
2332+
[2025-01-09 12:34:56] [ERROR] MyApp: Error without colors.
2333+
```
2334+
2335+
### Log messages to a file
2336+
```go
2337+
package main
2338+
2339+
import (
2340+
logging "github.com/kashifkhan0771/utils/logging"
2341+
"os"
2342+
)
2343+
2344+
func main() {
2345+
// Open a log file for writing
2346+
file, err := os.Create("app.log")
2347+
if err != nil {
2348+
panic(err)
2349+
}
2350+
defer file.Close()
2351+
2352+
// Create a logger with file output
2353+
logger := logging.NewLogger("MyApp", logging.DEBUG, file)
2354+
2355+
// Log messages
2356+
logger.Debug("Writing debug logs to file.")
2357+
logger.Info("Application log stored in file.")
2358+
logger.Warn("This is a warning.")
2359+
logger.Error("This is an error.")
2360+
}
2361+
```
2362+
#### Output (in `app.log` file):
2363+
```
2364+
[2025-01-09 12:34:56] [DEBUG] MyApp: Writing debug logs to file.
2365+
[2025-01-09 12:34:56] [INFO] MyApp: Application log stored in file.
2366+
[2025-01-09 12:34:56] [WARN] MyApp: This is a warning.
2367+
[2025-01-09 12:34:56] [ERROR] MyApp: This is an error.
2368+
```
2369+
2370+
### Filter logs by minimum log level
2371+
```go
2372+
package main
2373+
2374+
import (
2375+
logging "github.com/kashifkhan0771/utils/logging"
2376+
"os"
2377+
)
2378+
2379+
func main() {
2380+
// Create a logger with minimum level WARN
2381+
logger := logging.NewLogger("MyApp", logging.WARN, os.Stdout)
2382+
2383+
// Log messages
2384+
logger.Debug("This is a debug message.") // Ignored
2385+
logger.Info("This is an info message.") // Ignored
2386+
logger.Warn("This is a warning.") // Printed
2387+
logger.Error("This is an error.") // Printed
2388+
}
2389+
```
2390+
#### Output:
2391+
```
2392+
[2025-01-09 12:34:56] [WARN] MyApp: This is a warning.
2393+
[2025-01-09 12:34:56] [ERROR] MyApp: This is an error.
2394+
```
2395+
2396+
### Customize log prefixes
2397+
```go
2398+
package main
2399+
2400+
import (
2401+
logging "github.com/kashifkhan0771/utils/logging"
2402+
"os"
2403+
)
2404+
2405+
func main() {
2406+
// Create a logger with a custom prefix
2407+
logger := logging.NewLogger("CustomPrefix", logging.INFO, os.Stdout)
2408+
2409+
// Log messages
2410+
logger.Info("This message has a custom prefix.")
2411+
}
2412+
```
2413+
#### Output:
2414+
```
2415+
[2025-01-09 12:34:56] [INFO] CustomPrefix: This message has a custom prefix.
2416+
```

logging/logging.go

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
// Package logging provides a simple logging library with support for
2+
// multiple log levels, custom prefixes, colored output, and customizable
3+
// output streams.
4+
package logging
5+
6+
import (
7+
"fmt"
8+
"io"
9+
"os"
10+
"time"
11+
)
12+
13+
// ANSI color codes for different log levels
14+
const (
15+
ColorReset = "\033[0m" // Reset to default color
16+
ColorBlue = "\033[34m" // Blue color for INFO level logs
17+
ColorGreen = "\033[32m" // Green color for DEBUG level logs
18+
ColorYellow = "\033[33m" // Yellow color for WARN level logs
19+
ColorRed = "\033[31m" // Red color for ERROR level logs
20+
)
21+
22+
// LogLevel represents the severity level of a log message.
23+
type LogLevel int
24+
25+
// Defined log levels in increasing order of severity.
26+
const (
27+
DEBUG LogLevel = iota // DEBUG is used for detailed information during development.
28+
INFO // INFO is used for general informational messages.
29+
WARN // WARN is used for warnings that are not critical.
30+
ERROR // ERROR is used for critical error messages.
31+
)
32+
33+
// Logger is a configurable logging instance that supports multiple log levels,
34+
// optional colored output, and custom prefixes for log messages.
35+
type Logger struct {
36+
minLevel LogLevel // Minimum log level for messages to be logged
37+
prefix string // Prefix to prepend to all log messages
38+
output io.Writer // Output destination for log messages (e.g., os.Stdout)
39+
disableColors bool // Flag to disable color codes (useful for testing or non-ANSI terminals)
40+
}
41+
42+
// NewLogger creates and returns a new Logger instance with the specified prefix,
43+
// minimum log level, and output destination. If output is nil, os.Stdout is used
44+
// as the default destination.
45+
//
46+
// Parameters:
47+
// - prefix: A string prefix that will appear in all log messages.
48+
// - minLevel: The minimum log level for a message to be logged.
49+
// - output: The io.Writer to which log messages will be written.
50+
//
51+
// Returns:
52+
//
53+
// A pointer to a configured Logger instance.
54+
func NewLogger(prefix string, minLevel LogLevel, output io.Writer) *Logger {
55+
if output == nil {
56+
output = os.Stdout // Default to standard output
57+
}
58+
return &Logger{
59+
minLevel: minLevel,
60+
prefix: prefix,
61+
output: output,
62+
}
63+
}
64+
65+
// log handles the core logic of logging messages. It applies the appropriate
66+
// color coding (if enabled), formats the log message with a timestamp and prefix,
67+
// and writes it to the configured output destination.
68+
//
69+
// Parameters:
70+
// - level: The LogLevel of the message being logged.
71+
// - message: The actual log message to be recorded.
72+
func (l *Logger) log(level LogLevel, message string) {
73+
if level < l.minLevel {
74+
return
75+
}
76+
77+
// Determine the color and level string for the log level
78+
color := ""
79+
levelStr := ""
80+
switch level {
81+
case INFO:
82+
color = ColorBlue
83+
levelStr = "INFO"
84+
case DEBUG:
85+
color = ColorGreen
86+
levelStr = "DEBUG"
87+
case WARN:
88+
color = ColorYellow
89+
levelStr = "WARN"
90+
case ERROR:
91+
color = ColorRed
92+
levelStr = "ERROR"
93+
}
94+
95+
// Disable color if the flag is set
96+
if l.disableColors {
97+
color = ""
98+
}
99+
100+
// Format the timestamp for the log message
101+
timestamp := time.Now().Format("2006-01-02 15:04:05")
102+
103+
// Construct the formatted log message
104+
logMessage := fmt.Sprintf("%s[%s] [%s] %s: %s%s\n", color, timestamp, levelStr, l.prefix, message, ColorReset)
105+
106+
// Write the log message to the configured output
107+
_, err := fmt.Fprint(l.output, logMessage)
108+
if err != nil {
109+
fmt.Fprintf(os.Stderr, "Failed to write log: %v\n", err)
110+
}
111+
}
112+
113+
// Info logs a message at the INFO level.
114+
//
115+
// Parameters:
116+
// - message: The informational message to log.
117+
func (l *Logger) Info(message string) {
118+
l.log(INFO, message)
119+
}
120+
121+
// Debug logs a message at the DEBUG level.
122+
//
123+
// Parameters:
124+
// - message: The debug message to log.
125+
func (l *Logger) Debug(message string) {
126+
l.log(DEBUG, message)
127+
}
128+
129+
// Warn logs a message at the WARN level.
130+
//
131+
// Parameters:
132+
// - message: The warning message to log.
133+
func (l *Logger) Warn(message string) {
134+
l.log(WARN, message)
135+
}
136+
137+
// Error logs a message at the ERROR level.
138+
//
139+
// Parameters:
140+
// - message: The error message to log.
141+
func (l *Logger) Error(message string) {
142+
l.log(ERROR, message)
143+
}

logging/logging_test.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package logging_test
2+
3+
import (
4+
"bytes"
5+
"strings"
6+
"testing"
7+
8+
"github.com/kashifkhan0771/utils/logging"
9+
)
10+
11+
func TestLogger(t *testing.T) {
12+
type args struct {
13+
level logging.LogLevel
14+
message string
15+
}
16+
tests := []struct {
17+
name string
18+
minLevel logging.LogLevel
19+
args args
20+
wantOutput string
21+
}{
22+
{
23+
name: "success - log INFO when minLevel is INFO",
24+
minLevel: logging.INFO,
25+
args: args{level: logging.INFO, message: "This is an info message"},
26+
wantOutput: "INFO",
27+
},
28+
{
29+
name: "success - do not log DEBUG when minLevel is INFO",
30+
minLevel: logging.INFO,
31+
args: args{level: logging.DEBUG, message: "This is a debug message"},
32+
wantOutput: "",
33+
},
34+
{
35+
name: "success - log DEBUG when minLevel is DEBUG",
36+
minLevel: logging.DEBUG,
37+
args: args{level: logging.DEBUG, message: "This is a debug message"},
38+
wantOutput: "DEBUG",
39+
},
40+
{
41+
name: "success - log WARN when minLevel is WARN",
42+
minLevel: logging.WARN,
43+
args: args{level: logging.WARN, message: "This is a warning message"},
44+
wantOutput: "WARN",
45+
},
46+
{
47+
name: "success - do not log INFO when minLevel is WARN",
48+
minLevel: logging.WARN,
49+
args: args{level: logging.INFO, message: "This is an info message"},
50+
wantOutput: "",
51+
},
52+
{
53+
name: "success - log ERROR when minLevel is ERROR",
54+
minLevel: logging.ERROR,
55+
args: args{level: logging.ERROR, message: "This is an error message"},
56+
wantOutput: "ERROR",
57+
},
58+
{
59+
name: "success - do not log WARN when minLevel is ERROR",
60+
minLevel: logging.ERROR,
61+
args: args{level: logging.WARN, message: "This is a warning message"},
62+
wantOutput: "",
63+
},
64+
}
65+
66+
for _, tt := range tests {
67+
t.Run(tt.name, func(t *testing.T) {
68+
// Prepare a buffer to capture log output
69+
buffer := &bytes.Buffer{}
70+
logger := logging.NewLogger("Test", tt.minLevel, buffer)
71+
72+
// Log the message
73+
switch tt.args.level {
74+
case logging.INFO:
75+
logger.Info(tt.args.message)
76+
case logging.DEBUG:
77+
logger.Debug(tt.args.message)
78+
case logging.WARN:
79+
logger.Warn(tt.args.message)
80+
case logging.ERROR:
81+
logger.Error(tt.args.message)
82+
}
83+
84+
// Verify the output
85+
output := buffer.String()
86+
if tt.wantOutput == "" {
87+
if output != "" {
88+
t.Errorf("Expected no output, got: %v", output)
89+
}
90+
} else {
91+
if !strings.Contains(output, tt.wantOutput) {
92+
t.Errorf("Expected output to contain '%v', got: %v", tt.wantOutput, output)
93+
}
94+
}
95+
})
96+
}
97+
}

0 commit comments

Comments
 (0)