diff -pruN 0.1.0-2/README.md 0.1.0+git20220705+8771236337c6-1/README.md
--- 0.1.0-2/README.md	2019-03-31 20:37:50.000000000 +0000
+++ 0.1.0+git20220705+8771236337c6-1/README.md	2022-07-05 01:28:34.000000000 +0000
@@ -60,11 +60,7 @@ with jq. For string fields other than `l
   "logger": "HelloWorldService",
   "thread": "main",
   "message": "hello world",
-  "exceptions": [{
-    "module": "com.MyApp",
-    "type": "HelloException",
-    "stack_trace": [{...}]
-  }]
+  "exception": "java.lang.IllegalArgumentException: The world isn't here\n...stacktraces..."
 }
 ```
 See [log_example.json](./examples/log_example.json) for a more complete example.
@@ -80,6 +76,40 @@ See [log_example.json](./examples/log_ex
 }
 ```
 
+### Google Stackdriver
+
+Stackdriver logs require a little pre-processing from `jq`.
+
+```shell script
+gcloud logging read "resource.type=cloud_run_revision AND resource.labels.service_name=<YOUR_SERVICE>" --freshness=30m --format=json \
+  | jq -c -r 'reverse | .[]' \
+  | jl
+```
+
+```json
+{
+  "insertId": "5e864b4c000cc26666c50a3b",
+  "jsonPayload": {
+    "message": "hello world",
+    "foo": "bar"
+  },
+  "labels": {
+    "instanceId": "..."
+  },
+  "logName": "projects/<PROJECT>/logs/run.googleapis.com%2Fstderr",
+  "receiveTimestamp": "2020-04-02T20:30:05.116903175Z",
+  "resource": {
+    "labels": {
+        "location": "...",
+        "project_id": "...",
+    },
+    "type": "cloud_run_revision"
+  },
+  "severity": "INFO",
+  "timestamp": "2020-04-02T20:30:04.835224670Z"
+}
+```
+
 ## Roll your own format
 
 If the format that JL provides does not suit your needs, All of jl's functionality is available as
diff -pruN 0.1.0-2/color.go 0.1.0+git20220705+8771236337c6-1/color.go
--- 0.1.0-2/color.go	2019-03-31 20:37:50.000000000 +0000
+++ 0.1.0+git20220705+8771236337c6-1/color.go	1970-01-01 00:00:00.000000000 +0000
@@ -1,68 +0,0 @@
-package jl
-
-import (
-	"fmt"
-)
-
-type Color int
-
-// Foreground text colors
-const (
-	Black Color = iota + 30
-	Red
-	Green
-	Yellow
-	Blue
-	Magenta
-	Cyan
-	White
-)
-
-// Foreground Hi-Intensity text colors
-const (
-	HiBlack Color = iota + 90
-	HiRed
-	HiGreen
-	HiYellow
-	HiBlue
-	HiMagenta
-	HiCyan
-	HiWhite
-)
-
-// AllColors is the set of colors used by default by DefaultCompactFieldFmts for ColorSequence.
-var AllColors = []Color{
-	// Skipping black because it's invisible on dark terminal backgrounds.
-	// Skipping red because it's too prominent and means error
-	Green,
-	Yellow,
-	Blue,
-	Magenta,
-	Cyan,
-	White,
-	HiBlack,
-	HiRed,
-	HiGreen,
-	HiYellow,
-	HiBlue,
-	HiMagenta,
-	HiCyan,
-	HiWhite,
-}
-
-// ColorText wraps a text with ANSI escape codes to produce terminal colors.
-func ColorText(c Color, text string) string {
-	return fmt.Sprintf("\x1b[%dm%s\x1b[0m", c, text)
-}
-
-// LevelColors is a mapping of log level strings to colors.
-var LevelColors = map[string]Color{
-	"trace": White,
-	"debug": White,
-	"info": Green,
-	"warn": Yellow,
-	"warning": Yellow,
-	"error": Red,
-	"fatal": Red,
-	"panic": Red,
-}
diff -pruN 0.1.0-2/colorizer.go 0.1.0+git20220705+8771236337c6-1/colorizer.go
--- 0.1.0-2/colorizer.go	2019-03-31 20:37:50.000000000 +0000
+++ 0.1.0+git20220705+8771236337c6-1/colorizer.go	1970-01-01 00:00:00.000000000 +0000
@@ -1,56 +0,0 @@
-package jl
-
-import (
-	"strings"
-)
-
-type sequentialColorizer struct {
-	assigned map[string]Color
-	seq      int
-	colors   []Color
-}
-
-// ColorSequence assigns colors to inputs sequentially. Once an input is seen and assigned a color, future iputs with
-// the same value will always be assigned the same color.
-func ColorSequence(colors []Color) *sequentialColorizer {
-	return &sequentialColorizer{
-		assigned: make(map[string]Color),
-		colors:   colors,
-	}
-}
-
-func (a *sequentialColorizer) Transform(ctx *Context, input string) string {
-	if ctx.DisableColor {
-		return input
-	}
-	if color, ok := a.assigned[ctx.Original]; ok {
-		return ColorText(color, input)
-	}
-	color := a.colors[a.seq%len(AllColors)]
-	a.seq++
-	a.assigned[ctx.Original] = color
-	return ColorText(color, input)
-}
-
-type mappingColorizer struct {
-	mapping map[string]Color
-}
-
-// ColorMap assigns colors by mapping the original, pre-transform field value to a color based on a pre-defined mapping.
-func ColorMap(mapping map[string]Color) *mappingColorizer {
-	lowered := make(map[string]Color, len(mapping))
-	for k, v := range mapping {
-		lowered[strings.ToLower(k)] = v
-	}
-	return &mappingColorizer{lowered}
-}
-
-func (c *mappingColorizer) Transform(ctx *Context, input string) string {
-	if ctx.DisableColor {
-		return input
-	}
-	if color, ok := c.mapping[strings.ToLower(ctx.Original)]; ok {
-		return ColorText(color, input)
-	}
-	return input
-}
diff -pruN 0.1.0-2/colorizers.go 0.1.0+git20220705+8771236337c6-1/colorizers.go
--- 0.1.0-2/colorizers.go	1970-01-01 00:00:00.000000000 +0000
+++ 0.1.0+git20220705+8771236337c6-1/colorizers.go	2022-07-05 01:28:34.000000000 +0000
@@ -0,0 +1,56 @@
+package jl
+
+import (
+	"strings"
+)
+
+type sequentialColorizer struct {
+	assigned map[string]Color
+	seq      int
+	colors   []Color
+}
+
+// ColorSequence assigns colors to inputs sequentially. Once an input is seen and assigned a color, future iputs with
+// the same value will always be assigned the same color.
+func ColorSequence(colors []Color) *sequentialColorizer {
+	return &sequentialColorizer{
+		assigned: make(map[string]Color),
+		colors:   colors,
+	}
+}
+
+func (a *sequentialColorizer) Transform(ctx *Context, input string) string {
+	if ctx.DisableColor {
+		return input
+	}
+	if color, ok := a.assigned[ctx.Original]; ok {
+		return ColorText(color, input)
+	}
+	color := a.colors[a.seq%len(AllColors)]
+	a.seq++
+	a.assigned[ctx.Original] = color
+	return ColorText(color, input)
+}
+
+type mappingColorizer struct {
+	mapping map[string]Color
+}
+
+// ColorMap assigns colors by mapping the original, pre-transform field value to a color based on a pre-defined mapping.
+func ColorMap(mapping map[string]Color) *mappingColorizer {
+	lowered := make(map[string]Color, len(mapping))
+	for k, v := range mapping {
+		lowered[strings.ToLower(k)] = v
+	}
+	return &mappingColorizer{lowered}
+}
+
+func (c *mappingColorizer) Transform(ctx *Context, input string) string {
+	if ctx.DisableColor {
+		return input
+	}
+	if color, ok := c.mapping[strings.ToLower(ctx.Original)]; ok {
+		return ColorText(color, input)
+	}
+	return input
+}
diff -pruN 0.1.0-2/colors.go 0.1.0+git20220705+8771236337c6-1/colors.go
--- 0.1.0-2/colors.go	1970-01-01 00:00:00.000000000 +0000
+++ 0.1.0+git20220705+8771236337c6-1/colors.go	2022-07-05 01:28:34.000000000 +0000
@@ -0,0 +1,68 @@
+package jl
+
+import (
+	"fmt"
+)
+
+type Color int
+
+// Foreground text colors
+const (
+	Black Color = iota + 30
+	Red
+	Green
+	Yellow
+	Blue
+	Magenta
+	Cyan
+	White
+)
+
+// Foreground Hi-Intensity text colors
+const (
+	HiBlack Color = iota + 90
+	HiRed
+	HiGreen
+	HiYellow
+	HiBlue
+	HiMagenta
+	HiCyan
+	HiWhite
+)
+
+// AllColors is the set of colors used by default by DefaultCompactFieldFmts for ColorSequence.
+var AllColors = []Color{
+	// Skipping black because it's invisible on dark terminal backgrounds.
+	// Skipping red because it's too prominent and means error
+	Green,
+	Yellow,
+	Blue,
+	Magenta,
+	Cyan,
+	White,
+	HiBlack,
+	HiRed,
+	HiGreen,
+	HiYellow,
+	HiBlue,
+	HiMagenta,
+	HiCyan,
+	HiWhite,
+}
+
+// ColorText wraps a text with ANSI escape codes to produce terminal colors.
+func ColorText(c Color, text string) string {
+	return fmt.Sprintf("\x1b[%dm%s\x1b[0m", c, text)
+}
+
+// LevelColors is a mapping of log level strings to colors.
+var LevelColors = map[string]Color{
+	"trace": White,
+	"debug": White,
+	"info": Green,
+	"warn": Yellow,
+	"warning": Yellow,
+	"error": Red,
+	"fatal": Red,
+	"panic": Red,
+}
diff -pruN 0.1.0-2/compact_printer.go 0.1.0+git20220705+8771236337c6-1/compact_printer.go
--- 0.1.0-2/compact_printer.go	2019-03-31 20:37:50.000000000 +0000
+++ 0.1.0+git20220705+8771236337c6-1/compact_printer.go	2022-07-05 01:28:34.000000000 +0000
@@ -1,14 +1,25 @@
 package jl
 
 import (
-	"bytes"
-	"encoding/json"
 	"fmt"
 	"io"
 	"strings"
 	"unicode"
 )
 
+// CompactPrinter can print logs in a variety of compact formats, specified by FieldFormats.
+type CompactPrinter struct {
+	Out io.Writer
+	// Disable colors disables adding color to fields.
+	DisableColor bool
+	// Disable truncate disables the Ellipsize and Truncate transforms.
+	DisableTruncate bool
+	// FieldFormats specifies the format the printer should use for logs. It defaults to DefaultCompactPrinterFieldFmt. Fields
+	// are formatted in the order they are provided. If a FieldFmt produces a field that does not end with a whitespace,
+	// a space character is automatically appended.
+	FieldFormats []FieldFmt
+}
+
 // FieldFmt specifies a single field formatted by the CompactPrinter.
 type FieldFmt struct {
 	// Name of the field. This is used to find the field by key name if Finders is not set.
@@ -26,38 +37,30 @@ type FieldFmt struct {
 // for most types of logs.
 var DefaultCompactPrinterFieldFmt = []FieldFmt{{
 	Name:         "level",
+	Finders:      []FieldFinder{ByNames("level", "severity", "logLevel")},
 	Transformers: []Transformer{Truncate(4), UpperCase, ColorMap(LevelColors)},
 }, {
 	Name:    "time",
-	Finders: []FieldFinder{ByNames("timestamp", "time")},
+	Finders: []FieldFinder{ByNames("timestamp", "time", "ts")},
 }, {
 	Name:         "thread",
 	Transformers: []Transformer{Ellipsize(16), Format("[%s]"), RightPad(18), ColorSequence(AllColors)},
 }, {
 	Name:         "logger",
+	Finders:      []FieldFinder{ByNames("logger", "caller")},
 	Transformers: []Transformer{Ellipsize(20), Format("%s|"), LeftPad(21), ColorSequence(AllColors)},
 }, {
+	Name:         "traceId",
+	Transformers: []Transformer{Format("%s|"), ColorSequence(AllColors)},
+}, {
 	Name:    "message",
-	Finders: []FieldFinder{ByNames("message", "msg")},
+	Finders: []FieldFinder{ByNames("message", "msg", "textPayload", "jsonPayload.message")},
 }, {
 	Name:     "errors",
-	Finders:  []FieldFinder{JavaExceptionFinder, LogrusErrorFinder, ByNames("exceptions", "error")},
+	Finders:  []FieldFinder{LogrusErrorFinder, ByNames("exceptions", "exception", "error")},
 	Stringer: ErrorStringer,
 }}
 
-// CompactPrinter can print logs in a variety of compact formats, specified by FieldFormats.
-type CompactPrinter struct {
-	Out io.Writer
-	// Disable colors disables adding color to fields.
-	DisableColor bool
-	// Disable truncate disables the Ellipsize and Truncate transforms.
-	DisableTruncate bool
-	// FieldFormats specifies the format the printer should use for logs. It defaults to DefaultCompactPrinterFieldFmt. Fields
-	// are formatted in the order they are provided. If a FieldFmt produces a field that does not end with a whitespace,
-	// a space character is automatically appended.
-	FieldFormats []FieldFmt
-}
-
 // NewCompactPrinter allocates and returns a new compact printer.
 func NewCompactPrinter(w io.Writer) *CompactPrinter {
 	return &CompactPrinter{
@@ -130,119 +133,3 @@ func (f *FieldFmt) format(ctx *Context,
 
 	return s
 }
-
-// FieldFinder locates a field in the Entry and returns it.
-type FieldFinder func(entry *Entry) interface{}
-
-// ByNames locates fields by their top-level key name in the JSON log entry, and returns the field as a json.RawMessage.
-func ByNames(names ...string) FieldFinder {
-	return func(entry *Entry) interface{} {
-		for _, name := range names {
-			if v, ok := entry.Partials[name]; ok {
-				return v
-			}
-		}
-		return nil
-	}
-}
-
-// LogrusErrorFinder finds logrus error in the JSON log and returns it as a LogrusError.
-func LogrusErrorFinder(entry *Entry) interface{} {
-	var errStr, stack string
-	if errV, ok := entry.Partials["error"]; !ok {
-		return nil
-	} else if err := json.Unmarshal(errV, &errStr); err != nil {
-		return nil
-	}
-	if stackV, ok := entry.Partials["stack"]; !ok {
-		return nil
-	} else if err := json.Unmarshal(stackV, &stack); err != nil {
-		return nil
-	}
-	return LogrusError{errStr, stack}
-}
-
-// JavaExceptionFinder finds a Java exception containing a stracetrace and returns it as a JavaExceptions.
-func JavaExceptionFinder(entry *Entry) interface{} {
-	var java struct {
-		Exceptions []*JavaException `json:"exceptions"`
-	}
-	if err := json.Unmarshal(entry.Raw, &java); err == nil && len(java.Exceptions) > 0 {
-		return JavaExceptions(java.Exceptions)
-	}
-	return nil
-}
-
-// Stringer transforms a field returned by the FieldFinder into a string.
-type Stringer func(ctx *Context, v interface{}) string
-
-var _ = Stringer(DefaultStringer)
-var _ = Stringer(ErrorStringer)
-
-// DefaultStringer attempts to turn a field into string by attempting the following in order
-// 1. casting it to a string
-// 2. unmarshalling it as a json.RawMessage
-// 3. using fmt.Sprintf("%v", input)
-func DefaultStringer(ctx *Context, v interface{}) string {
-	var s string
-	if tmp, ok := v.(string); ok {
-		s = tmp
-	} else if rawMsg, ok := v.(json.RawMessage); ok {
-		var unmarshaled interface{}
-		if err := json.Unmarshal(rawMsg, &unmarshaled); err != nil {
-			s = string(rawMsg)
-		} else {
-			s = fmt.Sprintf("%v", unmarshaled)
-		}
-	} else {
-		s = fmt.Sprintf("%v", v)
-	}
-	return s
-}
-
-// ErrorStringer stringifies LogrusError, JavaExceptions to a multiline string. If the field is neither, it falls back
-// to the DefaultStringer.
-func ErrorStringer(ctx *Context, v interface{}) string {
-	w := &bytes.Buffer{}
-	if logrusErr, ok := v.(LogrusError); ok {
-		w.WriteString("\n  ")
-		w.WriteString(logrusErr.Error)
-		w.WriteRune('\n')
-		// left pad with a tab
-		lines := strings.Split(logrusErr.Stack, "\n")
-		stackStr := "\t" + strings.Join(lines, "\n\t")
-		w.WriteString(stackStr)
-		return w.String()
-	} else if exceptions, ok := v.(JavaExceptions); ok {
-		for i, e := range []*JavaException(exceptions) {
-			fmt.Fprint(w, "\n  ")
-			if i != 0 {
-				fmt.Fprint(w, "Caused by: ")
-			}
-			msg := e.Message
-			if !ctx.DisableColor {
-				msg = ColorText(Red, msg)
-			}
-			fmt.Fprintf(w, "%s.%s: %s", e.Module, e.Type, msg)
-			for _, stack := range e.StackTrace {
-				fmt.Fprintf(w, "\n    at %s.%s(%s:%d)", stack.Module, stack.Func, stack.File, stack.Line)
-			}
-			if e.FramesOmitted > 0 {
-				fmt.Fprintf(w, "\n    ...%d frames omitted...", e.FramesOmitted)
-			}
-		}
-		return w.String()
-	} else {
-		return DefaultStringer(ctx, v)
-	}
-}
-
-// Context provides the current transformation context, to be used by Transformers and Stringers.
-type Context struct {
-	// The original string before any transformations were applied.
-	Original string
-	// Indicates that terminal color escape sequences should be disabled.
-	DisableColor bool
-	// Indicates that fields should not be truncated.
-	DisableTruncate bool
-}
diff -pruN 0.1.0-2/compact_printer_test.go 0.1.0+git20220705+8771236337c6-1/compact_printer_test.go
--- 0.1.0-2/compact_printer_test.go	2019-03-31 20:37:50.000000000 +0000
+++ 0.1.0+git20220705+8771236337c6-1/compact_printer_test.go	2022-07-05 01:28:34.000000000 +0000
@@ -27,24 +27,16 @@ func TestCompactPrinter_Print(t *testing
 		formatted string
 	}{{
 		name:      "basic",
-		json:      `{"timestamp":"2019-01-01 15:23:45","level":"INFO","thread":"truck-manager","logger":"TruckRepairServiceOverlordManager","message":"There are 7 more trucks in the garage to fix. Get to work."}`,
-		formatted: "INFO 2019-01-01 15:23:45 [truck-manager]    TruckRepa…ordManager| There are 7 more trucks in the garage to fix. Get to work.\n",
+		json:      `{"timestamp":"2019-01-01 15:23:45","level":"INFO","thread":"truck-manager","traceId":"123125f435d657a1","logger":"TruckRepairServiceOverlordManager","message":"There are 7 more trucks in the garage to fix. Get to work."}`,
+		formatted: "INFO 2019-01-01 15:23:45 [truck-manager]    TruckRepa…ordManager| 123125f435d657a1| There are 7 more trucks in the garage to fix. Get to work.\n",
 	}, {
 		name: "exception",
-		json: `{"timestamp":"2019-01-01 15:34:45","level":"ERROR","thread":"repair-worker-2","logger":"TruckRepairMinion","message":"Truck 5 has is really broken! I'm need parts, waiting till they come.","exceptions":[{"frames_omitted":8,"message":"I can't do anything","module":"com.truckstop","type":"MustStandAroundException","stack_trace":[{"file":"TruckRepairMinion.java","func":"repair","line":84,"module":"com.truckstop"},{"file":"TruckRepairDepot.java","func":"makeMinionDoWork","line":1,"module":"com.truckstop"}]},{"frames_omitted":5,"message":"missing 3 parts","module":"com.truckstop","type":"MissingPartsException","stack_trace":[{"file":"TruckRepairChecklist.java","func":"checkParts","line":42,"module":"com.truckstop"},{"file":"TruckRepairMinion.java","func":"runChecklist","line":84,"module":"com.truckstop"}]}]}`,
-		formatted: `ERRO 2019-01-01 15:34:45 [repair-worker-2]     TruckRepairMinion| Truck 5 has is really broken! I'm need parts, waiting till they come.
-  com.truckstop.MustStandAroundException: I can't do anything
-    at com.truckstop.repair(TruckRepairMinion.java:84)
-    at com.truckstop.makeMinionDoWork(TruckRepairDepot.java:1)
-    ...8 frames omitted...
-  Caused by: com.truckstop.MissingPartsException: missing 3 parts
-    at com.truckstop.checkParts(TruckRepairChecklist.java:42)
-    at com.truckstop.runChecklist(TruckRepairMinion.java:84)
-    ...5 frames omitted...
+		json: `{"timestamp":"2019-01-01 15:34:45","level":"ERROR","thread":"repair-worker-2","traceId":"123125f435d657a1","logger":"TruckRepairMinion","message":"Truck 5 has is really broken! I'm need parts, waiting till they come."}`,
+		formatted: `ERRO 2019-01-01 15:34:45 [repair-worker-2]     TruckRepairMinion| 123125f435d657a1| Truck 5 has is really broken! I'm need parts, waiting till they come.
 `}, {
 		name: "logrus_pgk_error",
-		json: `{"timestamp":"2019-01-01 15:23:45","level":"error","message":"an error occurred","error":"BOOM!","stack":"github.com/pkg/errors_test.fn\n\t/home/dfc/src/github.com/pkg/errors/example_test.go:47\ngithub.com/pkg/errors_test.Example_stackTrace\n\t/home/dfc/src/github.com/pkg/errors/example_test.go:127\n"}`,
-		formatted: `ERRO 2019-01-01 15:23:45 an error occurred
+		json: `{"timestamp":"2019-01-01 15:23:45","level":"error","message":"an error occurred","traceId":"123125f435d657a1","error":"BOOM!","stack":"github.com/pkg/errors_test.fn\n\t/home/dfc/src/github.com/pkg/errors/example_test.go:47\ngithub.com/pkg/errors_test.Example_stackTrace\n\t/home/dfc/src/github.com/pkg/errors/example_test.go:127\n"}`,
+		formatted: `ERRO 2019-01-01 15:23:45 123125f435d657a1| an error occurred
   BOOM!
 	github.com/pkg/errors_test.fn
 		/home/dfc/src/github.com/pkg/errors/example_test.go:47
diff -pruN 0.1.0-2/debian/changelog 0.1.0+git20220705+8771236337c6-1/debian/changelog
--- 0.1.0-2/debian/changelog	2023-05-18 18:48:56.000000000 +0000
+++ 0.1.0+git20220705+8771236337c6-1/debian/changelog	2023-05-20 16:04:50.000000000 +0000
@@ -1,3 +1,10 @@
+golang-github-mightyguava-jl (0.1.0+git20220705+8771236337c6-1) unstable; urgency=medium
+
+  * New upstream snapshot.
+  * Update the manual page.
+
+ -- Andrej Shadura <andrewsh@debian.org>  Sat, 20 May 2023 18:04:50 +0200
+
 golang-github-mightyguava-jl (0.1.0-2) unstable; urgency=medium
 
   * Add a manpage.
diff -pruN 0.1.0-2/debian/jl.pod 0.1.0+git20220705+8771236337c6-1/debian/jl.pod
--- 0.1.0-2/debian/jl.pod	2023-05-18 18:48:56.000000000 +0000
+++ 0.1.0+git20220705+8771236337c6-1/debian/jl.pod	2023-05-20 16:04:50.000000000 +0000
@@ -76,11 +76,7 @@ Other formats can be used after preproce
       "logger": "HelloWorldService",
       "thread": "main",
       "message": "hello world",
-      "exceptions": [{
-        "module": "com.MyApp",
-        "type": "HelloException",
-        "stack_trace": [{...}]
-      }]
+      "exception": "java.lang.IllegalArgumentException: The world isn't here\n...stacktraces..."
     }
 
 =item * Go/Logrus-like:
diff -pruN 0.1.0-2/debian/watch 0.1.0+git20220705+8771236337c6-1/debian/watch
--- 0.1.0-2/debian/watch	2023-05-18 18:48:56.000000000 +0000
+++ 0.1.0+git20220705+8771236337c6-1/debian/watch	2023-05-20 16:04:50.000000000 +0000
@@ -2,3 +2,7 @@ version=4
 opts="filenamemangle=s%(?:.*?)?v?(\d[\d.]*)\.tar\.gz%@PACKAGE@-$1.tar.gz%,\
       uversionmangle=s/(\d)[_\.\-\+]?(RC|rc|pre|dev|beta|alpha)[.]?(\d*)$/$1~$2$3/" \
   https://github.com/mightyguava/jl/tags .*/v?(\d\S*)\.tar\.gz debian
+
+opts="mode=git, pgpmode=none, pretty=0.1.0+git%cd+%h, repack, compression=xz" \
+ https://github.com/mightyguava/jl.git \
+ HEAD
diff -pruN 0.1.0-2/doc.go 0.1.0+git20220705+8771236337c6-1/doc.go
--- 0.1.0-2/doc.go	1970-01-01 00:00:00.000000000 +0000
+++ 0.1.0+git20220705+8771236337c6-1/doc.go	2022-07-05 01:28:34.000000000 +0000
@@ -0,0 +1,4 @@
+/*
+Package jl is a parser and formatter for structured JSON logs.
+ */
+package jl
\ No newline at end of file
diff -pruN 0.1.0-2/errors.go 0.1.0+git20220705+8771236337c6-1/errors.go
--- 0.1.0-2/errors.go	2019-03-31 20:37:50.000000000 +0000
+++ 0.1.0+git20220705+8771236337c6-1/errors.go	2022-07-05 01:28:34.000000000 +0000
@@ -1,25 +1,5 @@
 package jl
 
-// JavaExceptions represents a list of Java exceptions, in the default JSON format.
-type JavaExceptions []*JavaException
-
-// JavaException contains a single Java exception in a causal chain.
-type JavaException struct {
-	FramesOmitted int64           `json:"frames_omitted"`
-	Message       string          `json:"message"`
-	Module        string          `json:"module"`
-	StackTrace    []JavaStackItem `json:"stack_trace"`
-	Type          string          `json:"type"`
-}
-
-// JavaStackItem is a single line in a stack trace.
-type JavaStackItem struct {
-	File   string `json:"file"`
-	Func   string `json:"func"`
-	Line   int64  `json:"line"`
-	Module string `json:"module"`
-}
-
 // LogrusError encapsulates a logrus style error
 type LogrusError struct {
 	Error string
diff -pruN 0.1.0-2/examples/log_example.json 0.1.0+git20220705+8771236337c6-1/examples/log_example.json
--- 0.1.0-2/examples/log_example.json	2019-03-31 20:37:50.000000000 +0000
+++ 0.1.0+git20220705+8771236337c6-1/examples/log_example.json	2022-07-05 01:28:34.000000000 +0000
@@ -9,11 +9,11 @@
 {"timestamp":"2019-01-01 15:31:45","level":"INFO","thread":"truck-manager-overseer-idle-loop","logger":"TruckRepairServiceOverlordManager","message":"There are 4 more trucks in the garage to fix. Get to work."}
 {"timestamp":"2019-01-01 15:32:45","level":"WARN","thread":"repair-worker-3","logger":"TruckRepairMinion","message":"I'm gonna go on break"}
 {"timestamp":"2019-01-01 15:33:45","level":"INFO","thread":"repair-worker-1","logger":"TruckRepairMinion","message":"I fixed truck 4!"}
-{"timestamp":"2019-01-01 15:34:45","level":"ERROR","thread":"repair-worker-2","logger":"TruckRepairMinion","message":"Truck 5 has is really broken! I'm need parts, waiting till they come.","exceptions":[{"frames_omitted":8,"message":"I can't do anything","module":"com.truckstop","type":"MustStandAroundException","stack_trace":[{"file":"TruckRepairMinion.java","func":"repair","line":84,"module":"com.truckstop"},{"file":"TruckRepairDepot.java","func":"makeMinionDoWork","line":1,"module":"com.truckstop"}]},{"frames_omitted":5,"message":"missing 3 parts","module":"com.truckstop","type":"MissingPartsException","stack_trace":[{"file":"TruckRepairChecklist.java","func":"checkParts","line":42,"module":"com.truckstop"},{"file":"TruckRepairMinion.java","func":"runChecklist","line":84,"module":"com.truckstop"}]}]}
+{"timestamp":"2019-01-01 15:34:45","level":"ERROR","thread":"repair-worker-2","logger":"TruckRepairMinion","message":"Truck 5 has is really broken! I'm need parts, waiting till they come."}
 {"timestamp":"2019-01-01 15:35:45","level":"INFO","thread":"repair-worker-1","logger":"TruckRepairMinion","message":"Fixing truck 6, it's got a broken axle"}
 {"timestamp":"2019-01-01 15:36:45","level":"INFO","thread":"truck-manager-overseer-idle-loop","logger":"TruckRepairServiceOverlordManager","message":"There are 3 more trucks in the garage to fix. Get to work."}
 {"timestamp":"2019-01-01 15:37:45","level":"INFO","thread":"repair-worker-1","logger":"TruckRepairMinion","message":"I fixed truck 6!"}
-{"timestamp":"2019-01-01 15:38:45","level":"ERROR","thread":"repair-worker-2","logger":"TruckRepairMinion","message":"Truck 5 has is really broken! I'm need parts, waiting till they come.","exceptions":[{"frames_omitted":8,"message":"I can't do anything","module":"com.truckstop","type":"MustStandAroundException","stack_trace":[{"file":"TruckRepairMinion.java","func":"repair","line":84,"module":"com.truckstop"},{"file":"TruckRepairDepot.java","func":"makeMinionDoWork","line":1,"module":"com.truckstop"}]},{"frames_omitted":5,"message":"missing 3 parts","module":"com.truckstop","type":"MissingPartsException","stack_trace":[{"file":"TruckRepairChecklist.java","func":"checkParts","line":42,"module":"com.truckstop"},{"file":"TruckRepairMinion.java","func":"runChecklist","line":84,"module":"com.truckstop"}]}]}
+{"timestamp":"2019-01-01 15:38:45","level":"ERROR","thread":"repair-worker-2","logger":"TruckRepairMinion","message":"Truck 5 has is really broken! I'm need parts, waiting till they come."}
 {"timestamp":"2019-01-01 15:39:45","level":"WARN","thread":"repair-worker-3","logger":"TruckRepairMinion","message":"I'm back from break"}
 {"timestamp":"2019-01-01 15:40:45","level":"INFO","thread":"repair-worker-3","logger":"TruckRepairMinion","message":"Fixing truck 7, it's got a broken axle"}
 {"timestamp":"2019-01-01 15:41:45","level":"INFO","thread":"repair-worker-1","logger":"TruckRepairMinion","message":"Fixing truck 6, it's got a broken axle"}
diff -pruN 0.1.0-2/finders.go 0.1.0+git20220705+8771236337c6-1/finders.go
--- 0.1.0-2/finders.go	1970-01-01 00:00:00.000000000 +0000
+++ 0.1.0+git20220705+8771236337c6-1/finders.go	2022-07-05 01:28:34.000000000 +0000
@@ -0,0 +1,58 @@
+package jl
+
+import (
+	"encoding/json"
+	"strings"
+)
+
+// FieldFinder locates a field in the Entry and returns it.
+type FieldFinder func(entry *Entry) interface{}
+
+// ByNames locates fields by their top-level key name in the JSON log entry, and returns the field as a json.RawMessage.
+func ByNames(names ...string) FieldFinder {
+	return func(entry *Entry) interface{} {
+		for _, name := range names {
+			if v, ok := getDeep(entry, name); ok {
+				return v
+			}
+		}
+		return nil
+	}
+}
+
+func getDeep(entry *Entry, name string) (interface{}, bool) {
+	parts := strings.SplitN(name, ".", 2)
+	key := parts[0]
+	v, ok := entry.Partials[key]
+	if !ok {
+		return nil, false
+	}
+	if len(parts) == 1 {
+		return v, true
+	}
+	var partials map[string]json.RawMessage
+	err := json.Unmarshal(v, &partials)
+	if err != nil {
+		return nil, false
+	}
+	return getDeep(&Entry{
+		Partials: partials,
+		Raw:      v,
+	}, parts[1])
+}
+
+// LogrusErrorFinder finds logrus error in the JSON log and returns it as a LogrusError.
+func LogrusErrorFinder(entry *Entry) interface{} {
+	var errStr, stack string
+	if errV, ok := entry.Partials["error"]; !ok {
+		return nil
+	} else if err := json.Unmarshal(errV, &errStr); err != nil {
+		return nil
+	}
+	if stackV, ok := entry.Partials["stack"]; !ok {
+		return nil
+	} else if err := json.Unmarshal(stackV, &stack); err != nil {
+		return nil
+	}
+	return LogrusError{errStr, stack}
+}
diff -pruN 0.1.0-2/stringers.go 0.1.0+git20220705+8771236337c6-1/stringers.go
--- 0.1.0-2/stringers.go	1970-01-01 00:00:00.000000000 +0000
+++ 0.1.0+git20220705+8771236337c6-1/stringers.go	2022-07-05 01:28:34.000000000 +0000
@@ -0,0 +1,53 @@
+package jl
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"strings"
+)
+
+// Stringer transforms a field returned by the FieldFinder into a string.
+type Stringer func(ctx *Context, v interface{}) string
+
+var _ = Stringer(DefaultStringer)
+var _ = Stringer(ErrorStringer)
+
+// DefaultStringer attempts to turn a field into string by attempting the following in order
+// 1. casting it to a string
+// 2. unmarshalling it as a json.RawMessage
+// 3. using fmt.Sprintf("%v", input)
+func DefaultStringer(ctx *Context, v interface{}) string {
+	var s string
+	if tmp, ok := v.(string); ok {
+		s = tmp
+	} else if rawMsg, ok := v.(json.RawMessage); ok {
+		var unmarshaled interface{}
+		if err := json.Unmarshal(rawMsg, &unmarshaled); err != nil {
+			s = string(rawMsg)
+		} else {
+			s = fmt.Sprintf("%v", unmarshaled)
+		}
+	} else {
+		s = fmt.Sprintf("%v", v)
+	}
+	return s
+}
+
+// ErrorStringer stringifies LogrusError to a multiline string. If the field is not a LogrusError, it falls back
+// to the DefaultStringer.
+func ErrorStringer(ctx *Context, v interface{}) string {
+	w := &bytes.Buffer{}
+	if logrusErr, ok := v.(LogrusError); ok {
+		w.WriteString("\n  ")
+		w.WriteString(logrusErr.Error)
+		w.WriteRune('\n')
+		// left pad with a tab
+		lines := strings.Split(logrusErr.Stack, "\n")
+		stackStr := "\t" + strings.Join(lines, "\n\t")
+		w.WriteString(stackStr)
+		return w.String()
+	}  else {
+		return DefaultStringer(ctx, v)
+	}
+}
diff -pruN 0.1.0-2/transform.go 0.1.0+git20220705+8771236337c6-1/transform.go
--- 0.1.0-2/transform.go	2019-03-31 20:37:50.000000000 +0000
+++ 0.1.0+git20220705+8771236337c6-1/transform.go	2022-07-05 01:28:34.000000000 +0000
@@ -7,6 +7,16 @@ import (
 	"unicode/utf8"
 )
 
+// Context provides the current transformation context, to be used by Transformers and Stringers.
+type Context struct {
+	// The original string before any transformations were applied.
+	Original string
+	// Indicates that terminal color escape sequences should be disabled.
+	DisableColor bool
+	// Indicates that fields should not be truncated.
+	DisableTruncate bool
+}
+
 // Transformer transforms a string and returns the result.
 type Transformer interface {
 	Transform(ctx *Context, input string) string
