package meowlib import ( "bytes" "encoding/json" "fmt" "net/http" "strings" "time" ) type LokiWriter struct { url string labels map[string]string httpClient *http.Client } type LokiPayload struct { Streams []LokiStream `json:"streams"` } type LokiStream struct { Labels string `json:"labels"` Entries []LokiEntry `json:"entries"` } type LokiEntry struct { Timestamp string `json:"ts"` Line string `json:"line"` } func NewLokiWriter(url string, labels map[string]string) *LokiWriter { return &LokiWriter{ url: url, labels: labels, httpClient: &http.Client{}, } } func (w *LokiWriter) Write(p []byte) (n int, err error) { // Format log entry for Loki entry := LokiEntry{ Timestamp: time.Now().Format(time.RFC3339Nano), Line: string(p), } labels := []string{} for k, v := range w.labels { labels = append(labels, fmt.Sprintf(`%s="%s"`, k, v)) } labelString := "{" + strings.Join(labels, ",") + "}" stream := LokiStream{ Labels: labelString, Entries: []LokiEntry{entry}, } payload := LokiPayload{ Streams: []LokiStream{stream}, } payloadBytes, err := json.Marshal(payload) if err != nil { return 0, fmt.Errorf("failed to marshal payload: %w", err) } req, err := http.NewRequest("POST", w.url, bytes.NewReader(payloadBytes)) if err != nil { return 0, fmt.Errorf("failed to create HTTP request: %w", err) } req.Header.Set("Content-Type", "application/json") resp, err := w.httpClient.Do(req) if err != nil { return 0, fmt.Errorf("failed to send log to Loki: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusNoContent { return 0, fmt.Errorf("received non-204 response from Loki: %d", resp.StatusCode) } return len(p), nil }