Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ gtag('config', '${gaMeasurementId}');`,
items: [
{ text: 'Axiom', link: '/transports/axiom' },
{ text: 'Datadog', link: '/transports/datadog' },
{ text: 'New Relic', link: '/transports/newrelic' },
{ text: 'Google Cloud Logging', link: '/transports/gcplogging' },
{ text: 'Sentry', link: '/transports/sentry' },
],
Expand Down
1 change: 1 addition & 0 deletions docs/src/transports/_partials/transport-list.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Managed log services. Async + batched by default; site-aware where applicable.
| [Axiom](/transports/axiom) | [![Version](https://img.shields.io/github/v/tag/loglayer/loglayer-go?filter=transports/axiom/v*&sort=date&label=version&style=flat-square&color=blue)](https://github.com/loglayer/loglayer-go/releases?q=transports/axiom/&expanded=true) | [![Go Reference](https://pkg.go.dev/badge/go.loglayer.dev/transports/axiom/v2.svg)](https://pkg.go.dev/go.loglayer.dev/transports/axiom/v2) | Ships logs to Axiom via caller-supplied `*axiom.Client`. NDJSON ingestion with configurable message field. |
| [Datadog](/transports/datadog) | [![Version](https://img.shields.io/github/v/tag/loglayer/loglayer-go?filter=transports/datadog/v*&sort=date&label=version&style=flat-square&color=blue)](https://github.com/loglayer/loglayer-go/releases?q=transports/datadog/&expanded=true) | [![Go Reference](https://pkg.go.dev/badge/go.loglayer.dev/transports/datadog/v2.svg)](https://pkg.go.dev/go.loglayer.dev/transports/datadog/v2) | Datadog Logs HTTP intake. Site-aware URL, DD-API-KEY header, status mapping. |
| [Google Cloud Logging](/transports/gcplogging) | [![Version](https://img.shields.io/github/v/tag/loglayer/loglayer-go?filter=transports/gcplogging/v*&sort=date&label=version&style=flat-square&color=blue)](https://github.com/loglayer/loglayer-go/releases?q=transports/gcplogging/&expanded=true) | [![Go Reference](https://pkg.go.dev/badge/go.loglayer.dev/transports/gcplogging/v2.svg)](https://pkg.go.dev/go.loglayer.dev/transports/gcplogging/v2) | Forwards entries to a caller-supplied `*logging.Logger` from `cloud.google.com/go/logging`. Severity mapping, root-level Entry skeleton, async + sync dispatch. |
| [New Relic](/transports/newrelic) | [![Version](https://img.shields.io/github/v/tag/loglayer/loglayer-go?filter=transports/newrelic/v*&sort=date&label=version&style=flat-square&color=blue)](https://github.com/loglayer/loglayer-go/releases?q=transports/newrelic/&expanded=true) | [![Go Reference](https://pkg.go.dev/badge/go.loglayer.dev/transports/newrelic/v2.svg)](https://pkg.go.dev/go.loglayer.dev/transports/newrelic/v2) | New Relic Log API. Zone-aware URL, Api-Key header, NDJSON encoder. |
| [Sentry](/transports/sentry) | [![Version](https://img.shields.io/github/v/tag/loglayer/loglayer-go?filter=transports/sentry/v*&sort=date&label=version&style=flat-square&color=blue)](https://github.com/loglayer/loglayer-go/releases?q=transports/sentry/&expanded=true) | [![Go Reference](https://pkg.go.dev/badge/go.loglayer.dev/transports/sentry/v2.svg)](https://pkg.go.dev/go.loglayer.dev/transports/sentry/v2) | Forwards entries to a `sentry.Logger`. Routes fatal/panic through `LFatal` so loglayer's core controls termination. |

</div>
Expand Down
82 changes: 82 additions & 0 deletions docs/src/transports/newrelic.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
---
title: New Relic Transport
description: Ships log entries to New Relic Log API as NDJSON.
---

# New Relic Transport

<ModuleBadges path="transports/newrelic" />

The `newrelic` transport ships log entries to the [New Relic Log API](https://docs.newrelic.com/docs/logs/log-api/introduction-log-api/) as NDJSON. Authentication is via the `Api-Key` header from `Config.APIKey` by default. `X-License-Key` is optionally supported for accounts that require it. Built on `transports/http` for async batching.

```sh
go get go.loglayer.dev/transports/newrelic
```

## Basic Usage

```go
import (
"go.loglayer.dev/v2"
"go.loglayer.dev/transports/newrelic/v2"
)

tr := newrelic.New(newrelic.Config{
APIKey: "your-new-relic-api-key",
Zone: newrelic.ZoneUS, // optional; ZoneUS is the default
})

log := loglayer.New(loglayer.Config{Transport: tr})
log.Info("hello")

defer tr.Close() // flushes pending entries
```

## Config

| Field | Type | Default | Description |
|--------------|---------------------|---------|-------------|
| `APIKey` | `string` | (required) | New Relic user key for the `Api-Key` header |
| `LicenseKey` | `string` | empty | Optional `X-License-Key` header value |
| `Zone` | `newrelic.IntakeZone` | `ZoneUS` | Region selection |
| `URL` | `string` | derived from `Zone` | Custom endpoint override |
| `Hostname` | `string` | empty | Per-entry `hostname` attribute |
| `AllowInsecureURL` | `bool` | false | Permit non-https `Config.URL` |
| `HTTP` | `httptr.Config` | batching defaults | HTTP transport overrides |

`Config.HTTP.URL` and `Config.HTTP.Encoder` cannot be set (the transport sets them itself). Custom headers go through `Config.HTTP.Headers`.

### Zone

| Constant | Region | Endpoint |
|------------|---------------|----------------------------------------------|
| `ZoneUS` | US (default) | `https://log-api.newrelic.com/log/v1` |
| `ZoneEU` | EU | `https://log-api.eu.newrelic.com/log/v1` |

### API Key and License Key

The `APIKey` and `LicenseKey` fields are tagged `json:"-"` and redacted in `Config.String()` to prevent accidental exposure when the config is logged or passed through a JSON transport.

## Log Format

Each entry serializes as a JSON object with `timestamp`, `message`, `loglevel`, and optionally `hostname`. All user fields and metadata merge as root-level attributes. Lines are newline-delimited (NDJSON).

```json
{"hostname":"prod-web-01","loglevel":"warning","message":"high latency","requestId":"abc","timestamp":"2026-04-26T12:00:00.000Z"}
```

### Level Mapping

| LogLayer Level | New Relic level |
|------------------|-----------------|
| `LogLevelTrace` | `debug` |
| `LogLevelDebug` | `debug` |
| `LogLevelInfo` | `info` |
| `LogLevelWarn` | `warning` |
| `LogLevelError` | `error` |
| `LogLevelFatal` | `critical` |
| `LogLevelPanic` | `critical` |

## Fatal Behavior

<!--@include: ./_partials/fatal-passthrough.md-->
6 changes: 6 additions & 0 deletions docs/src/whats-new.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ description: Latest features and improvements in LogLayer for Go.

- See the [main `CHANGELOG.md`](https://github.com/loglayer/loglayer-go/blob/main/CHANGELOG.md) for the auto-generated per-release log.

## May 10, 2026

`transports/newrelic`:

Initial release. New [New Relic transport](/transports/newrelic).

## May 06, 2026

`transports/axiom`:
Expand Down
1 change: 1 addition & 0 deletions go.work
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use (
./transports/datadog
./transports/gcplogging
./transports/lumberjack
./transports/newrelic
./transports/http
./transports/logrus
./transports/otellog
Expand Down
5 changes: 5 additions & 0 deletions monorel.toml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ tag_prefix = "transports/lumberjack"
path = "transports/lumberjack"
changelog = "transports/lumberjack/CHANGELOG.md"

[packages."transports/newrelic"]
tag_prefix = "transports/newrelic"
path = "transports/newrelic"
changelog = "transports/newrelic/CHANGELOG.md"

[packages."transports/otellog"]
tag_prefix = "transports/otellog"
path = "transports/otellog"
Expand Down
3 changes: 3 additions & 0 deletions scripts/foreach-module.sh
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ ALL_MODULES=(
transports/datadog
transports/gcplogging
transports/lumberjack
transports/newrelic
transports/http
transports/logrus
transports/otellog
Expand Down Expand Up @@ -81,6 +82,7 @@ SHIPPED_MODULES=(
transports/datadog
transports/gcplogging
transports/lumberjack
transports/newrelic
transports/http
transports/logrus
transports/otellog
Expand Down Expand Up @@ -172,6 +174,7 @@ case "$op" in
transports/datadog
transports/gcplogging
transports/lumberjack
transports/newrelic
transports/http
transports/logrus
transports/otellog
Expand Down
4 changes: 2 additions & 2 deletions transports/axiom/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
go.loglayer.dev/v2 v2.0.1 h1:B7oYpkfMky0UG/N8IdKeIiiTi6h7eyj5NvRyEth9DHI=
go.loglayer.dev/v2 v2.0.1/go.mod h1:+BWhs5AyICvCLBz07qHnCE12W34tArUWfXOba0Ct/QI=
go.loglayer.dev/v2 v2.1.0 h1:8/fq8Z1NNLtjfKgaPn6S0Hy7zWPnkjn9Gj3YgEDFk4w=
go.loglayer.dev/v2 v2.1.0/go.mod h1:+BWhs5AyICvCLBz07qHnCE12W34tArUWfXOba0Ct/QI=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o=
Expand Down
3 changes: 3 additions & 0 deletions transports/newrelic/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Changelog

<!-- Do not edit this file manually. It is generated by monorel release. -->
17 changes: 17 additions & 0 deletions transports/newrelic/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# go.loglayer.dev/transports/newrelic

[![Go Reference](https://pkg.go.dev/badge/go.loglayer.dev/transports/newrelic/v2.svg)](https://pkg.go.dev/go.loglayer.dev/transports/newrelic/v2)

New Relic Log API transport for LogLayer. Built on `transports/http` with a New Relic-specific NDJSON encoder, zone-aware URL, and `Api-Key` header. Also supports `X-License-Key` for accounts that require it.

## Install

```sh
go get go.loglayer.dev/transports/newrelic
```

## Documentation

Full reference and examples: <https://go.loglayer.dev/transports/newrelic>

Main library: <https://go.loglayer.dev>
20 changes: 20 additions & 0 deletions transports/newrelic/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package newrelic

import "errors"

// ErrAPIKeyRequired is returned by Build (and panicked by New) when
// Config.APIKey is empty.
var ErrAPIKeyRequired = errors.New("loglayer/transports/newrelic: Config.APIKey is required")

// ErrHTTPOverrideForbidden is returned by Build (and panicked by New)
// when Config.HTTP.URL or Config.HTTP.Encoder is non-zero. The New Relic
// transport sets these itself (URL from the fixed Log API endpoint,
// Encoder from the package's NDJSON builder); a value supplied via the
// embedded HTTP config would be silently dropped, which used to surprise
// callers. The Encoder cannot be customized.
var ErrHTTPOverrideForbidden = errors.New("loglayer/transports/newrelic: Config.HTTP.URL and Config.HTTP.Encoder are managed by this package and must be left zero")

// ErrInsecureURL is returned by Build (and panicked by New) when
// Config.URL has a non-https scheme. The New Relic API key would be sent
// in cleartext over http; refuse rather than ship credentials in the open.
var ErrInsecureURL = errors.New("loglayer/transports/newrelic: Config.URL must use https")
23 changes: 23 additions & 0 deletions transports/newrelic/example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package newrelic_test

import (
"go.loglayer.dev/transports/newrelic/v2"
"go.loglayer.dev/v2"
)

// New ships log entries to the New Relic Log API. APIKey is required; Zone
// selects the regional endpoint (defaults to ZoneUS). The transport spawns a
// worker goroutine; call Close on shutdown to flush pending entries.
func ExampleNew() {
t := newrelic.New(newrelic.Config{
APIKey: "your-new-relic-api-key",
Zone: newrelic.ZoneUS,
})
defer t.Close()

log := loglayer.New(loglayer.Config{
Transport: t,
DisableFatalExit: true,
})
log.Info("served")
}
14 changes: 14 additions & 0 deletions transports/newrelic/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module go.loglayer.dev/transports/newrelic/v2

go 1.25.0

replace go.loglayer.dev/v2 => ../..

replace go.loglayer.dev/transports/http/v2 => ../http

require (
github.com/goccy/go-json v0.10.6
go.loglayer.dev/transports/http/v2 v2.0.0-00010101000000-000000000000
go.loglayer.dev/v2 v2.0.1
go.uber.org/goleak v1.3.0
)
12 changes: 12 additions & 0 deletions transports/newrelic/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU=
github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
23 changes: 23 additions & 0 deletions transports/newrelic/goleak_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package newrelic_test

import (
"testing"

"go.uber.org/goleak"
)

// TestMain wraps the suite in goleak.VerifyTestMain to catch goroutine
// leaks. The New Relic transport wraps an HTTP transport that spawns
// a worker goroutine per Transport via the internal async worker; tests
// must call tr.Close() to shut it down.
//
// httptest's connection-shutdown goroutines occasionally outlive the
// test cleanup, so we ignore the http.(*Server).Shutdown stacks. Any
// other unexpected goroutine fails the suite.
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m,
goleak.IgnoreTopFunction("net/http.(*Server).Shutdown"),
goleak.IgnoreAnyFunction("net/http.(*persistConn).readLoop"),
goleak.IgnoreAnyFunction("net/http.(*persistConn).writeLoop"),
)
}
Loading
Loading