๐Ÿ“ฆ EqualifyEverything / equalify-iris-wp

๐Ÿ“„ class-logger.php ยท 111 lines
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111<?php
/**
 * WHAT IS THIS FILE?
 *
 * A short activity log the admin can read in the dashboard.
 *
 * WHY DOES IT EXIST?
 *
 * Because the first question anyone asks about a background process is "what is
 * it actually doing?", and the honest answer has to come from somewhere. Without
 * this, a stalled queue and a healthy-but-idle queue look identical.
 *
 * It is deliberately tiny: the last 200 events, kept in a single network option,
 * oldest dropped as new ones arrive. That is called a ring buffer. It cannot
 * grow without bound, cannot need its own database table, and cannot become the
 * reason a site runs out of disk โ€” all of which are real risks for a logger that
 * writes a row per event forever on a network converting tens of thousands of
 * documents.
 *
 * This is a log for humans, not a debugging trace. Each entry is a full sentence
 * an admin can act on. Detailed per-document errors live on the document row
 * itself, where the retry button is.
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class Equalify_Iris_Logger {

	/** The network option the log lives in. */
	const OPTION = 'equalify_iris_activity_log';

	/**
	 * How many entries to keep.
	 *
	 * 200 is enough to cover a day of normal activity, and small enough that the
	 * whole option stays well under the size where storing it becomes slow.
	 */
	const MAX_ENTRIES = 200;

	/** Something worth knowing happened. */
	const INFO = 'info';

	/** Something went wrong that an admin may need to act on. */
	const ERROR = 'error';

	/** Something is off but the plugin is handling it, e.g. backing off. */
	const WARNING = 'warning';

	/**
	 * Record one event.
	 *
	 * @param string $message A complete sentence in plain language. This goes on
	 *                        screen unedited, so write it for an admin who has
	 *                        never read the code โ€” no error codes, no jargon.
	 * @param string $level   One of the INFO / WARNING / ERROR constants above.
	 * @param array  $context Optional extra detail shown alongside the message,
	 *                        such as which site or document it concerns.
	 */
	public static function log( string $message, string $level = self::INFO, array $context = array() ): void {
		$entries = self::entries();

		$entries[] = array(
			'time'    => time(),
			'level'   => $level,
			'message' => $message,
			'context' => $context,
		);

		// Keep only the newest MAX_ENTRIES. array_slice with a negative length
		// counted from the end is the whole ring buffer.
		if ( count( $entries ) > self::MAX_ENTRIES ) {
			$entries = array_slice( $entries, -self::MAX_ENTRIES );
		}

		update_site_option( self::OPTION, $entries );
	}

	/** Shorthand for an error-level entry. */
	public static function error( string $message, array $context = array() ): void {
		self::log( $message, self::ERROR, $context );
	}

	/** Shorthand for a warning-level entry. */
	public static function warning( string $message, array $context = array() ): void {
		self::log( $message, self::WARNING, $context );
	}

	/**
	 * Every entry, oldest first.
	 */
	public static function entries(): array {
		$entries = get_site_option( self::OPTION, array() );

		return is_array( $entries ) ? $entries : array();
	}

	/**
	 * Every entry, newest first โ€” which is the order the dashboard shows them in.
	 */
	public static function recent(): array {
		return array_reverse( self::entries() );
	}

	/** Empty the log. */
	public static function clear(): void {
		update_site_option( self::OPTION, array() );
	}
}