diff --git a/lib/logger/utils.spec.ts b/lib/logger/utils.spec.ts
index 376584700b8f4012d6e0d3bf2e94612854952f14..ac944c5ad2ca9c49c0d9d90c61d7f2cb30704a2b 100644
--- a/lib/logger/utils.spec.ts
+++ b/lib/logger/utils.spec.ts
@@ -1,4 +1,9 @@
-import { sanitizeValue, validateLogLevel } from './utils';
+import { z } from 'zod';
+import prepareError, {
+  prepareZodIssues,
+  sanitizeValue,
+  validateLogLevel,
+} from './utils';
 
 describe('logger/utils', () => {
   afterEach(() => {
@@ -46,4 +51,138 @@ describe('logger/utils', () => {
   `('sanitizeValue("$input") == "$output"', ({ input, output }) => {
     expect(sanitizeValue(input)).toBe(output);
   });
+
+  describe('prepareError', () => {
+    function getError<T extends z.ZodType>(
+      schema: T,
+      input: unknown
+    ): z.ZodError | null {
+      try {
+        schema.parse(input);
+      } catch (error) {
+        if (error instanceof z.ZodError) {
+          return error;
+        }
+      }
+      throw new Error('Expected error');
+    }
+
+    function prepareIssues<T extends z.ZodType>(
+      schema: T,
+      input: unknown
+    ): unknown | null {
+      const error = getError(schema, input);
+      return error ? prepareZodIssues(error.format()) : null;
+    }
+
+    it('prepareZodIssues', () => {
+      expect(prepareIssues(z.string(), 42)).toBe(
+        'Expected string, received number'
+      );
+
+      expect(prepareIssues(z.string().array(), 42)).toBe(
+        'Expected array, received number'
+      );
+
+      expect(
+        prepareIssues(z.string().array(), ['foo', 'bar', 42, 42, 42, 42, 42])
+      ).toEqual({
+        '2': 'Expected string, received number',
+        '3': 'Expected string, received number',
+        '4': 'Expected string, received number',
+        ___: '... 2 more',
+      });
+
+      expect(
+        prepareIssues(z.record(z.string()), {
+          foo: 'foo',
+          bar: 'bar',
+          key1: 42,
+          key2: 42,
+          key3: 42,
+          key4: 42,
+          key5: 42,
+        })
+      ).toEqual({
+        key1: 'Expected string, received number',
+        key2: 'Expected string, received number',
+        key3: 'Expected string, received number',
+        ___: '... 2 more',
+      });
+
+      expect(
+        prepareIssues(
+          z.object({
+            foo: z.object({
+              bar: z.string(),
+            }),
+          }),
+          { foo: { bar: [], baz: 42 } }
+        )
+      ).toEqual({
+        foo: {
+          bar: 'Expected string, received array',
+        },
+      });
+
+      expect(
+        prepareIssues(
+          z.discriminatedUnion('type', [
+            z.object({ type: z.literal('foo') }),
+            z.object({ type: z.literal('bar') }),
+          ]),
+          { type: 'baz' }
+        )
+      ).toEqual({
+        type: "Invalid discriminator value. Expected 'foo' | 'bar'",
+      });
+
+      expect(
+        prepareIssues(
+          z.discriminatedUnion('type', [
+            z.object({ type: z.literal('foo') }),
+            z.object({ type: z.literal('bar') }),
+          ]),
+          {}
+        )
+      ).toEqual({
+        type: "Invalid discriminator value. Expected 'foo' | 'bar'",
+      });
+
+      expect(
+        prepareIssues(
+          z.discriminatedUnion('type', [
+            z.object({ type: z.literal('foo') }),
+            z.object({ type: z.literal('bar') }),
+          ]),
+          42
+        )
+      ).toBe('Expected object, received number');
+    });
+
+    it('prepareError', () => {
+      const err = getError(
+        z.object({
+          foo: z.object({
+            bar: z.object({
+              baz: z.string(),
+            }),
+          }),
+        }),
+        { foo: { bar: { baz: 42 } } }
+      );
+
+      expect(prepareError(err!)).toEqual({
+        issues: {
+          foo: {
+            bar: {
+              baz: 'Expected string, received number',
+            },
+          },
+        },
+        message: 'Schema error',
+        stack: expect.stringMatching(/^ZodError: Schema error/),
+      });
+    });
+  });
 });
diff --git a/lib/logger/utils.ts b/lib/logger/utils.ts
index 4d0297cedb823bdb1bd7ccfd9a8c10458375bf9c..84144229650e3779541fabd76404f49d32ab70f5 100644
--- a/lib/logger/utils.ts
+++ b/lib/logger/utils.ts
@@ -3,6 +3,7 @@ import is from '@sindresorhus/is';
 import bunyan from 'bunyan';
 import fs from 'fs-extra';
 import { RequestError as HttpError } from 'got';
+import { ZodError } from 'zod';
 import { redactedFields, sanitize } from '../util/sanitize';
 import type { BunyanRecord, BunyanStream } from './types';
 
@@ -46,7 +47,74 @@ const contentFields = [
   'yarnLockParsed',
 ];
 
+type ZodShortenedIssue =
+  | null
+  | string
+  | string[]
+  | {
+      [key: string]: ZodShortenedIssue;
+    };
+
+export function prepareZodIssues(input: unknown): ZodShortenedIssue {
+  // istanbul ignore if
+  if (!is.plainObject(input)) {
+    return null;
+  }
+
+  let err: null | string | string[] = null;
+  if (is.array(input._errors, is.string)) {
+    // istanbul ignore else
+    if (input._errors.length === 1) {
+      err = input._errors[0];
+    } else if (input._errors.length > 1) {
+      err = input._errors;
+    } else {
+      err = null;
+    }
+  }
+  delete input._errors;
+
+  if (is.emptyObject(input)) {
+    return err;
+  }
+
+  const output: Record<string, ZodShortenedIssue> = {};
+  const entries = Object.entries(input);
+  for (const [key, value] of entries.slice(0, 3)) {
+    const child = prepareZodIssues(value);
+    if (child !== null) {
+      output[key] = child;
+    }
+  }
+
+  if (entries.length > 3) {
+    output['___'] = `... ${entries.length - 3} more`;
+  }
+
+  return output;
+}
+
+export function prepareZodError(err: ZodError): Record<string, unknown> {
+  // istanbul ignore next
+  Object.defineProperty(err, 'message', {
+    get: () => 'Schema error',
+    set: (_) => {
+      _;
+    },
+  });
+
+  return {
+    message: err.message,
+    stack: err.stack,
+    issues: prepareZodIssues(err.format()),
+  };
+}
+
 export default function prepareError(err: Error): Record<string, unknown> {
+  if (err instanceof ZodError) {
+    return prepareZodError(err);
+  }
+
   const response: Record<string, unknown> = {
     ...err,
   };