| 251 | } |
| 252 | |
| 253 | func (lw *logWriter) Write(buf []byte) (int, error) { |
| 254 | // this works but ONLY because stdcopy calls write a whole line at a time. |
| 255 | // if this ends up horribly broken or panics, check to see if stdcopy has |
| 256 | // reneged on that assumption. (@god forgive me) |
| 257 | // also this only works because the logs format is, like, barely parsable. |
| 258 | // if something changes in the logs format, this is gonna break |
| 259 | |
| 260 | // there should always be at least 2 parts: details and message. if there |
| 261 | // is no timestamp, details will be first (index 0) when we split on |
| 262 | // spaces. if there is a timestamp, details will be 2nd (`index 1) |
| 263 | detailsIndex := 0 |
| 264 | numParts := 2 |
| 265 | if lw.opts.timestamps { |
| 266 | detailsIndex++ |
| 267 | numParts++ |
| 268 | } |
| 269 | |
| 270 | // break up the log line into parts. |
| 271 | parts := bytes.SplitN(buf, []byte(" "), numParts) |
| 272 | if len(parts) != numParts { |
| 273 | return 0, fmt.Errorf("invalid context in log message: %v", string(buf)) |
| 274 | } |
| 275 | // parse the details out |
| 276 | details, err := logdetails.Parse(string(parts[detailsIndex])) |
| 277 | if err != nil { |
| 278 | return 0, err |
| 279 | } |
| 280 | // and then create a context from the details |
| 281 | // this removes the context-specific details from the details map, so we |
| 282 | // can more easily print the details later |
| 283 | logCtx, err := parseContext(details) |
| 284 | if err != nil { |
| 285 | return 0, err |
| 286 | } |
| 287 | |
| 288 | output := []byte{} |
| 289 | // if we included timestamps, add them to the front |
| 290 | if lw.opts.timestamps { |
| 291 | output = append(output, parts[0]...) |
| 292 | output = append(output, ' ') |
| 293 | } |
| 294 | // add the context, nice and formatted |
| 295 | formatted, err := lw.f.format(lw.ctx, logCtx) |
| 296 | if err != nil { |
| 297 | return 0, err |
| 298 | } |
| 299 | output = append(output, []byte(formatted+" | ")...) |
| 300 | // if the user asked for details, add them to be log message |
| 301 | if lw.opts.details { |
| 302 | // ugh i hate this it's basically a dupe of api/server/httputils/write_log_stream.go:stringAttrs() |
| 303 | // ok but we're gonna do it a bit different |
| 304 | |
| 305 | // there are optimizations that can be made here. for starters, i'd |
| 306 | // suggest caching the details keys. then, we can maybe draw maps and |
| 307 | // slices from a pool to avoid alloc overhead on them. idk if it's |
| 308 | // worth the time yet. |
| 309 | |
| 310 | // first we need a slice |