# Streamplace Go conventions How we write Go in Streamplace. The linter may not catch these conventions, but we expect you to follow them in this repo. ## Logging Log through `stream.place/streamplace/pkg/log`. Each call takes the context first, then the message, then alternating key-value pairs: ```go log.Log(ctx, "started ingest", "streamer", did, "rtmpPort", port) ``` Pick the level by what happened. The number is the glog `-v` level that a process needs to print the line. The default is `-v=3`. At the default, `Error`, `Warn`, and `Log` always print. `Debug` and `Trace` print only at a higher `-v`. | Call | min `-v` | Use it for | | ----------- | -------- | -------------------------------------------- | | `log.Error` | 1 | An operation failed and could not complete. | | `log.Warn` | 2 | Something was wrong, but the code recovered. | | `log.Log` | 3 | General information. This is the info level. | | `log.Debug` | 4 | Detail for debugging. Prints only at `-v=4`. | | `log.Trace` | 9 | Detailed tracing. Prints only at `-v=9`. | There is no `log.Info`. `log.Log` is the info level. Common mistakes: - The key-value arguments come in pairs. An odd number of arguments prints a warning at runtime and may corrupt the line. Always check the arguments after you edit a call. - Do not pass a `caller` key. The logger adds one for you. A value such as a request id or a streamer did can apply to every log line from a context. To attach such a value, derive a new context with `log.WithLogValues`. You do not need to pass the value to every call: ```go ctx = log.WithLogValues(ctx, "streamer", did) ``` ## Error wrapping Wrap errors with `%w`, not `%v`, so callers can inspect the cause with `errors.Is` and `errors.As`: ```go return fmt.Errorf("failed to combine segments: %w", err) ``` `%v` converts the error to a string and breaks the error chain. A caller can no longer match the underlying error. Use `%v` only when you want to hide the cause. ## HTTP handlers Write error responses with the helpers in `stream.place/streamplace/pkg/errors`. Do not call `w.WriteHeader` and encode JSON yourself, as the function will do it for you: ```go if err != nil { errors.WriteHTTPBadRequest(w, "Error parsing PUSH_REWRITE payload", err) return } ``` | Helper | Status | Notes | | ------------------------------- | ------ | ----------------------- | | `WriteHTTPBadRequest` | 400 | | | `WriteHTTPUnauthorized` | 401 | | | `WriteHTTPForbidden` | 403 | | | `WriteHTTPNotFound` | 404 | | | `WriteHTTPUnsupportedMediaType` | 415 | | | `WriteHTTPTooManyRequests` | 429 | takes no `err` argument | | `WriteHTTPInternalServerError` | 500 | | | `WriteHTTPNotImplemented` | 501 | | Each helper returns an `APIError{Msg, Status, Err}`. Most handlers ignore it and return after they write the response. Use the return value only when the caller needs the status or the message. ## Tests Assert with `github.com/stretchr/testify/require`, not `assert`. `require` fails the test immediately (`t.FailNow`) on the first broken precondition. A failed setup then stops at once, instead of causing nil-pointer panics in the assertions that follow: ```go require.NoError(t, err) require.Equal(t, "sync", cmd.Name) ``` Media tests using `withNoGSTLeaks` must stop their pipelines and cancel their contexts before returning. The leak helper waits for finalizer passes before requesting the native snapshot; keep that barrier synchronous and never block the finalizer queue, which also releases Go-owned GStreamer references. Verify changes to the helper with race-enabled media tests without disabling leak assertions. `WriterNewSample` releases each appsink sample when its callback returns, after unmapping its buffer. The go-gst v1.4 `GstSample` wrapper contains only a pointer to an opaque C type; on amd64 it is an 8-byte, GC-pointer-free allocation. Go's tiny allocator can pack it beside another live object, preventing its finalizer from running even after repeated GC. Clear the binding's finalizer before explicitly releasing that transferred reference, or it may be released twice. This is sample ownership, not a requirement to wrap every `Buffer.Map` call.