aboutsummaryrefslogtreecommitdiffstats
path: root/lib/private/Log/File.php
blob: 28cc856b98037b5a74436b629316394ba5878c9f (plain)
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
112
113
114
115
116
117
118
119
120
<?php

/**
 * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
 * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
 * SPDX-License-Identifier: AGPL-3.0-only
 */
namespace OC\Log;

use OC\SystemConfig;
use OCP\ILogger;
use OCP\Log\IFileBased;
use OCP\Log\IWriter;

/**
 * logging utilities
 *
 * Log is saved at data/nextcloud.log (on default)
 */

class File extends LogDetails implements IWriter, IFileBased {
	protected string $logFile;

	protected int $logFileMode;

	public function __construct(
		string $path,
		string $fallbackPath,
		private SystemConfig $config,
	) {
		parent::__construct($config);
		$this->logFile = $path;
		if (!file_exists($this->logFile)) {
			if (
				(
					!is_writable(dirname($this->logFile))
					|| !touch($this->logFile)
				)
				&& $fallbackPath !== ''
			) {
				$this->logFile = $fallbackPath;
			}
		}
		$this->logFileMode = $config->getValue('logfilemode', 0640);
	}

	/**
	 * write a message in the log
	 * @param string|array $message
	 */
	public function write(string $app, $message, int $level): void {
		$entry = $this->logDetailsAsJSON($app, $message, $level);
		$handle = @fopen($this->logFile, 'a');
		if ($this->logFileMode > 0 && is_file($this->logFile) && (fileperms($this->logFile) & 0777) != $this->logFileMode) {
			@chmod($this->logFile, $this->logFileMode);
		}
		if ($handle) {
			fwrite($handle, $entry."\n");
			fclose($handle);
		} else {
			// Fall back to error_log
			error_log($entry);
		}
		if (php_sapi_name() === 'cli-server') {
			if (!\is_string($message)) {
				$message = json_encode($message);
			}
			error_log($message, 4);
		}
	}

	/**
	 * get entries from the log in reverse chronological order
	 */
	public function getEntries(int $limit = 50, int $offset = 0): array {
		$minLevel = $this->config->getValue("loglevel", ILogger::WARN);
		$entries = [];
		$handle = @fopen($this->logFile, 'rb');
		if ($handle) {
			fseek($handle, 0, SEEK_END);
			$pos = ftell($handle);
			$line = '';
			$entriesCount = 0;
			$lines = 0;
			// Loop through each character of the file looking for new lines
			while ($pos >= 0 && ($limit === null || $entriesCount < $limit)) {
				fseek($handle, $pos);
				$ch = fgetc($handle);
				if ($ch == "\n" || $pos == 0) {
					if ($line != '') {
						// Add the first character if at the start of the file,
						// because it doesn't hit the else in the loop
						if ($pos == 0) {
							$line = $ch.$line;
						}
						$entry = json_decode($line);
						// Add the line as an entry if it is passed the offset and is equal or above the log level
						if ($entry->level >= $minLevel) {
							$lines++;
							if ($lines > $offset) {
								$entries[] = $entry;
								$entriesCount++;
							}
						}
						$line = '';
					}
				} else {
					$line = $ch.$line;
				}
				$pos--;
			}
			fclose($handle);
		}
		return $entries;
	}

	public function getLogFilePath():string {
		return $this->logFile;
	}
}