|
1 | 1 | # Gecho |
2 | 2 |
|
3 | | - |
| 3 | +A Go library for structured logging and consistent JSON HTTP responses. |
4 | 4 |
|
| 5 | +## Install |
| 6 | + |
| 7 | +```bash |
| 8 | +go get github.com/MonkyMars/gecho |
| 9 | +``` |
5 | 10 |
|
6 | | -A lightweight JSON response builder for Go HTTP handlers. |
| 11 | +## What It Does |
7 | 12 |
|
8 | | -## What it is |
| 13 | +Gecho provides two main features: |
9 | 14 |
|
10 | | -`gecho` provides a lightweight, functional options-based API for building and sending consistent JSON HTTP responses from Go handlers. It offers success and error helpers, customizable message/data/status, and built-in method validation helpers using a pattern similar to structured logging. |
| 15 | +1. **Structured Logger** - A thread-safe logger with multiple output formats and log levels |
| 16 | +2. **HTTP Response Builder** - Consistent JSON responses for HTTP handlers |
11 | 17 |
|
12 | | -## Install |
| 18 | +## Response Handling |
13 | 19 |
|
14 | | -Requires Go toolchain. |
| 20 | +### Basic Usage |
15 | 21 |
|
16 | | -```bash |
17 | | -go get github.com/MonkyMars/gecho |
| 22 | +```go |
| 23 | +import "github.com/MonkyMars/gecho" |
| 24 | + |
| 25 | +func handler(w http.ResponseWriter, r *http.Request) { |
| 26 | + // Success response |
| 27 | + gecho.Success(w, gecho.Send()) |
| 28 | + |
| 29 | + // Success with data |
| 30 | + gecho.Success(w, |
| 31 | + gecho.WithData(map[string]any{"id": 1, "name": "Alice"}), |
| 32 | + gecho.Send(), |
| 33 | + ) |
| 34 | + |
| 35 | + // Error response |
| 36 | + gecho.NotFound(w, |
| 37 | + gecho.WithMessage("User not found"), |
| 38 | + gecho.Send(), |
| 39 | + ) |
| 40 | +} |
18 | 41 | ``` |
19 | 42 |
|
20 | | -## Quick example |
| 43 | +### Available Functions |
21 | 44 |
|
22 | | -```go |
23 | | -import ( |
24 | | - "github.com/MonkyMars/gecho" |
25 | | -) |
| 45 | +**Success Responses:** |
| 46 | +- `Success(w, opts...)` - 200 OK |
| 47 | +- `Created(w, opts...)` - 201 Created |
| 48 | +- `Accepted(w, opts...)` - 202 Accepted |
| 49 | +- `NoContent(w, opts...)` - 204 No Content |
26 | 50 |
|
27 | | -// Success response with data |
28 | | -gecho.Success(w, |
29 | | - gecho.WithData(map[string]any{"id": 1, "name": "Alice"}), |
30 | | - gecho.Send(), |
31 | | -) |
| 51 | +**Error Responses:** |
| 52 | +- `BadRequest(w, opts...)` - 400 Bad Request |
| 53 | +- `Unauthorized(w, opts...)` - 401 Unauthorized |
| 54 | +- `Forbidden(w, opts...)` - 403 Forbidden |
| 55 | +- `NotFound(w, opts...)` - 404 Not Found |
| 56 | +- `MethodNotAllowed(w, opts...)` - 405 Method Not Allowed |
| 57 | +- `Conflict(w, opts...)` - 409 Conflict |
| 58 | +- `InternalServerError(w, opts...)` - 500 Internal Server Error |
| 59 | +- `ServiceUnavailable(w, opts...)` - 503 Service Unavailable |
32 | 60 |
|
33 | | -// Error response with custom message |
34 | | -gecho.BadRequest(w, |
35 | | - gecho.WithMessage("invalid input"), |
36 | | - gecho.Send(), |
37 | | -) |
| 61 | +### Options |
38 | 62 |
|
39 | | -// Multiple options |
40 | | -gecho.NotFound(w, |
41 | | - gecho.WithMessage("User not found"), |
42 | | - gecho.WithData(map[string]string{"resource": "users", "id": "123"}), |
43 | | - gecho.Send(), |
44 | | -) |
45 | | -``` |
| 63 | +- `WithData(data any)` - Add data to response |
| 64 | +- `WithMessage(msg string)` - Override default message |
| 65 | +- `WithStatus(code int)` - Override default status code |
| 66 | +- `Send()` - Send the response (required) |
46 | 67 |
|
47 | | -## Response Format |
| 68 | +### Response Format |
48 | 69 |
|
49 | | -All responses return a consistent JSON structure: |
| 70 | +All responses return this JSON structure: |
50 | 71 |
|
51 | 72 | ```json |
52 | 73 | { |
53 | 74 | "status": 200, |
54 | 75 | "success": true, |
55 | 76 | "message": "Success", |
56 | | - "data": { |
57 | | - "id": 1, |
58 | | - "name": "Alice" |
59 | | - }, |
| 77 | + "data": {"id": 1, "name": "Alice"}, |
60 | 78 | "timestamp": "2024-01-15T10:30:45.123Z" |
61 | 79 | } |
62 | 80 | ``` |
63 | 81 |
|
64 | | -## Available Options |
| 82 | +## Logger |
65 | 83 |
|
66 | | -- `gecho.WithData(data any)` - Set response data |
67 | | -- `gecho.WithMessage(message string)` - Override the default message |
68 | | -- `gecho.WithStatus(status int)` - Override the default status code |
69 | | -- `gecho.Send()` - Send the response immediately |
| 84 | +### Basic Usage |
| 85 | + |
| 86 | +```go |
| 87 | +import "github.com/MonkyMars/gecho" |
70 | 88 |
|
71 | | -## Common Usage Patterns |
| 89 | +func main() { |
| 90 | + // Create logger with defaults |
| 91 | + logger := gecho.NewDefaultLogger() |
| 92 | + |
| 93 | + // Log messages |
| 94 | + logger.Info("Server starting") |
| 95 | + logger.Error("Failed to connect") |
| 96 | + |
| 97 | + // Log with fields |
| 98 | + logger.Info("User logged in", |
| 99 | + gecho.Field("user_id", 123), |
| 100 | + gecho.Field("ip", "192.168.1.1"), |
| 101 | + ) |
| 102 | +} |
| 103 | +``` |
72 | 104 |
|
73 | | -### Success Responses |
| 105 | +### Configuration |
74 | 106 |
|
75 | 107 | ```go |
76 | | -// 200 OK |
77 | | -gecho.Success(w, gecho.WithData(userData), gecho.Send()) |
| 108 | +// Custom configuration |
| 109 | +config := gecho.NewConfig( |
| 110 | + gecho.WithLogLevel(gecho.LevelDebug), |
| 111 | + gecho.WithLogFormat(gecho.FormatJSON), |
| 112 | + gecho.WithShowCaller(true), |
| 113 | +) |
| 114 | +logger := gecho.NewLogger(config) |
| 115 | + |
| 116 | +// Change level at runtime |
| 117 | +logger.SetLevel(gecho.ParseLogLevel("debug")) |
| 118 | +``` |
78 | 119 |
|
79 | | -// 201 Created |
80 | | -gecho.Created(w, gecho.WithData(newResource), gecho.Send()) |
| 120 | +### Configuration Options |
81 | 121 |
|
82 | | -// 202 Accepted |
83 | | -gecho.Accepted(w, gecho.Send()) |
| 122 | +- `WithLogLevel(level Level)` - Set minimum log level (default: `LevelInfo`) |
| 123 | +- `WithLogFormat(format Format)` - Set output format (default: `FormatPretty`) |
| 124 | +- `WithColorize(bool)` - Enable/disable colored output (default: auto-detected) |
| 125 | +- `WithShowCaller(bool)` - Show/hide file and line number (default: `true`) |
| 126 | +- `WithTimeFormat(string)` - Custom time format (default: `"2006-01-02 15:04:05.000"`) |
| 127 | +- `WithOutput(io.Writer)` - Set output destination (default: `os.Stdout`) |
| 128 | +- `WithErrorOutput(io.Writer)` - Set error output destination (default: `os.Stderr`) |
| 129 | +- `WithDefaultCallerSkip(int)` - Adjust call stack depth for caller info (default: `2`) |
84 | 130 |
|
85 | | -// 204 No Content |
86 | | -gecho.NoContent(w, gecho.Send()) |
87 | | -``` |
| 131 | +### Log Levels |
| 132 | + |
| 133 | +- `LevelDebug` - Debug messages |
| 134 | +- `LevelInfo` - Informational messages |
| 135 | +- `LevelWarn` - Warning messages |
| 136 | +- `LevelError` - Error messages |
| 137 | +- `LevelFatal` - Fatal errors (exits program) |
| 138 | + |
| 139 | +### Output Formats |
88 | 140 |
|
89 | | -### Error Responses |
| 141 | +- `FormatText` - Plain text with fields |
| 142 | +- `FormatJSON` - JSON output |
| 143 | +- `FormatPretty` - Colored output with parentheses format (default) |
| 144 | + |
| 145 | +### Persistent Fields |
90 | 146 |
|
91 | 147 | ```go |
92 | | -// 400 Bad Request |
93 | | -gecho.BadRequest(w, gecho.WithData(validationErrors), gecho.Send()) |
| 148 | +// Create logger with persistent fields |
| 149 | +requestLogger := logger.WithFields(map[string]any{ |
| 150 | + "request_id": "abc123", |
| 151 | + "user_id": 456, |
| 152 | +}) |
| 153 | + |
| 154 | +requestLogger.Info("Processing request") // All logs include request_id and user_id |
| 155 | +``` |
94 | 156 |
|
95 | | -// 401 Unauthorized |
96 | | -gecho.Unauthorized(w, gecho.Send()) |
| 157 | +### HTTP Logging Middleware |
97 | 158 |
|
98 | | -// 403 Forbidden |
99 | | -gecho.Forbidden(w, gecho.Send()) |
| 159 | +```go |
| 160 | +func main() { |
| 161 | + mux := http.NewServeMux() |
| 162 | + mux.HandleFunc("/", handler) |
| 163 | + |
| 164 | + logger := gecho.NewDefaultLogger() |
| 165 | + loggedHandler := gecho.Handlers.HandleLogging(mux, logger) |
| 166 | + |
| 167 | + http.ListenAndServe(":8080", loggedHandler) |
| 168 | +} |
| 169 | +``` |
100 | 170 |
|
101 | | -// 404 Not Found |
102 | | -gecho.NotFound(w, gecho.Send()) |
| 171 | +Logs include method, path, status, duration, and remote address. |
103 | 172 |
|
104 | | -// 500 Internal Server Error |
105 | | -gecho.InternalServerError(w, gecho.Send()) |
| 173 | +## Method Validation |
| 174 | + |
| 175 | +```go |
| 176 | +func handler(w http.ResponseWriter, r *http.Request) { |
| 177 | + // Only allow POST requests |
| 178 | + if err := gecho.Handlers.HandleMethod(w, r, http.MethodPost); err != nil { |
| 179 | + return // Error response already sent |
| 180 | + } |
| 181 | + |
| 182 | + // Handle POST request |
| 183 | +} |
106 | 184 | ``` |
107 | 185 |
|
108 | | -### Full Example |
| 186 | +## Full Example |
109 | 187 |
|
110 | 188 | ```go |
111 | | -func getUserHandler(w http.ResponseWriter, r *http.Request) { |
112 | | - id := r.URL.Query().Get("id") |
| 189 | +package main |
| 190 | + |
| 191 | +import ( |
| 192 | + "net/http" |
| 193 | + "github.com/MonkyMars/gecho" |
| 194 | +) |
| 195 | + |
| 196 | +func main() { |
| 197 | + mux := http.NewServeMux() |
| 198 | + mux.HandleFunc("/users", getUsers) |
113 | 199 |
|
114 | | - if id == "" { |
115 | | - gecho.BadRequest(w, |
116 | | - gecho.WithMessage("Missing user ID"), |
117 | | - gecho.Send(), |
118 | | - ) |
| 200 | + logger := gecho.NewDefaultLogger() |
| 201 | + loggedHandler := gecho.Handlers.HandleLogging(mux, logger) |
| 202 | + |
| 203 | + http.ListenAndServe(":8080", loggedHandler) |
| 204 | +} |
| 205 | + |
| 206 | +func getUsers(w http.ResponseWriter, r *http.Request) { |
| 207 | + if err := gecho.Handlers.HandleMethod(w, r, http.MethodGet); err != nil { |
119 | 208 | return |
120 | 209 | } |
121 | 210 |
|
122 | | - user, err := findUser(id) |
123 | | - if err != nil { |
124 | | - gecho.NotFound(w, utils.Send()) |
125 | | - return |
| 211 | + users := []map[string]any{ |
| 212 | + {"id": 1, "name": "Alice"}, |
| 213 | + {"id": 2, "name": "Bob"}, |
126 | 214 | } |
127 | 215 |
|
128 | 216 | gecho.Success(w, |
129 | | - gecho.WithData(user), |
| 217 | + gecho.WithData(map[string]any{"users": users}), |
130 | 218 | gecho.Send(), |
131 | 219 | ) |
132 | 220 | } |
133 | 221 | ``` |
134 | 222 |
|
135 | | -For more detailed examples and documentation, see [USAGE.md](USAGE.md). |
136 | | - |
137 | | -## Project layout |
| 223 | +## Project Structure |
138 | 224 |
|
139 | | -- `gecho.go` — package entry points |
140 | | -- `errors/` — error response helpers |
141 | | -- `success/` — success response helpers |
142 | | -- `handlers/` — small built-in handlers (method validation) |
143 | | -- `utils/` — core builder and JSON logic |
| 225 | +- `gecho.go` - Main package exports |
| 226 | +- `errors/` - Error response functions |
| 227 | +- `success/` - Success response functions |
| 228 | +- `handlers/` - HTTP middleware and utilities |
| 229 | +- `utils/` - Core response builder and logger |
144 | 230 |
|
145 | 231 | ## Contributing |
146 | 232 |
|
147 | | -Contributions welcome:) Please open issues or pull requests and include a short description and tests for behavior changes. |
| 233 | +Contributions are welcome. Open an issue or pull request with a description and tests for changes. |
148 | 234 |
|
149 | | -## Important |
| 235 | +## Note |
150 | 236 |
|
151 | | -This library is provided as is. Backward compatibility is not guaranteed. Breaking changes may occur. |
| 237 | +This library is provided as-is. Breaking changes may occur between versions. |
0 commit comments