Log.php 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * @copyright Copyright (c) 2016, ownCloud, Inc.
  5. *
  6. * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
  7. * @author Bart Visscher <bartv@thisnet.nl>
  8. * @author Bernhard Posselt <dev@bernhard-posselt.com>
  9. * @author Joas Schilling <coding@schilljs.com>
  10. * @author Johannes Schlichenmaier <johannes@schlichenmaier.info>
  11. * @author Juan Pablo Villafáñez <jvillafanez@solidgear.es>
  12. * @author Lukas Reschke <lukas@statuscode.ch>
  13. * @author Morris Jobke <hey@morrisjobke.de>
  14. * @author Olivier Paroz <github@oparoz.com>
  15. * @author Robin Appelman <robin@icewind.nl>
  16. * @author Thomas Müller <thomas.mueller@tmit.eu>
  17. * @author Thomas Pulzer <t.pulzer@kniel.de>
  18. * @author Victor Dubiniuk <dubiniuk@owncloud.com>
  19. *
  20. * @license AGPL-3.0
  21. *
  22. * This code is free software: you can redistribute it and/or modify
  23. * it under the terms of the GNU Affero General Public License, version 3,
  24. * as published by the Free Software Foundation.
  25. *
  26. * This program is distributed in the hope that it will be useful,
  27. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  28. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  29. * GNU Affero General Public License for more details.
  30. *
  31. * You should have received a copy of the GNU Affero General Public License, version 3,
  32. * along with this program. If not, see <http://www.gnu.org/licenses/>
  33. *
  34. */
  35. namespace OC;
  36. use InterfaSys\LogNormalizer\Normalizer;
  37. use OCP\ILogger;
  38. use OCP\Support\CrashReport\IRegistry;
  39. use OCP\Util;
  40. /**
  41. * logging utilities
  42. *
  43. * This is a stand in, this should be replaced by a Psr\Log\LoggerInterface
  44. * compatible logger. See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md
  45. * for the full interface specification.
  46. *
  47. * MonoLog is an example implementing this interface.
  48. */
  49. class Log implements ILogger {
  50. /** @var string */
  51. private $logger;
  52. /** @var SystemConfig */
  53. private $config;
  54. /** @var boolean|null cache the result of the log condition check for the request */
  55. private $logConditionSatisfied = null;
  56. /** @var Normalizer */
  57. private $normalizer;
  58. /** @var IRegistry */
  59. private $crashReporters;
  60. protected $methodsWithSensitiveParameters = [
  61. // Session/User
  62. 'completeLogin',
  63. 'login',
  64. 'checkPassword',
  65. 'checkPasswordNoLogging',
  66. 'loginWithPassword',
  67. 'updatePrivateKeyPassword',
  68. 'validateUserPass',
  69. 'loginWithToken',
  70. '\{closure\}',
  71. // TokenProvider
  72. 'getToken',
  73. 'isTokenPassword',
  74. 'getPassword',
  75. 'decryptPassword',
  76. 'logClientIn',
  77. 'generateToken',
  78. 'validateToken',
  79. // TwoFactorAuth
  80. 'solveChallenge',
  81. 'verifyChallenge',
  82. // ICrypto
  83. 'calculateHMAC',
  84. 'encrypt',
  85. 'decrypt',
  86. // LoginController
  87. 'tryLogin',
  88. 'confirmPassword',
  89. // LDAP
  90. 'bind',
  91. 'areCredentialsValid',
  92. 'invokeLDAPMethod',
  93. // Encryption
  94. 'storeKeyPair',
  95. 'setupUser',
  96. ];
  97. /**
  98. * @param string $logger The logger that should be used
  99. * @param SystemConfig $config the system config object
  100. * @param Normalizer|null $normalizer
  101. * @param IRegistry|null $registry
  102. */
  103. public function __construct($logger = null, SystemConfig $config = null, $normalizer = null, IRegistry $registry = null) {
  104. // FIXME: Add this for backwards compatibility, should be fixed at some point probably
  105. if($config === null) {
  106. $config = \OC::$server->getSystemConfig();
  107. }
  108. $this->config = $config;
  109. // FIXME: Add this for backwards compatibility, should be fixed at some point probably
  110. if($logger === null) {
  111. $logType = $this->config->getValue('log_type', 'file');
  112. $this->logger = static::getLogClass($logType);
  113. call_user_func([$this->logger, 'init']);
  114. } else {
  115. $this->logger = $logger;
  116. }
  117. if ($normalizer === null) {
  118. $this->normalizer = new Normalizer();
  119. } else {
  120. $this->normalizer = $normalizer;
  121. }
  122. $this->crashReporters = $registry;
  123. }
  124. /**
  125. * System is unusable.
  126. *
  127. * @param string $message
  128. * @param array $context
  129. * @return void
  130. */
  131. public function emergency(string $message, array $context = []) {
  132. $this->log(Util::FATAL, $message, $context);
  133. }
  134. /**
  135. * Action must be taken immediately.
  136. *
  137. * Example: Entire website down, database unavailable, etc. This should
  138. * trigger the SMS alerts and wake you up.
  139. *
  140. * @param string $message
  141. * @param array $context
  142. * @return void
  143. */
  144. public function alert(string $message, array $context = []) {
  145. $this->log(Util::ERROR, $message, $context);
  146. }
  147. /**
  148. * Critical conditions.
  149. *
  150. * Example: Application component unavailable, unexpected exception.
  151. *
  152. * @param string $message
  153. * @param array $context
  154. * @return void
  155. */
  156. public function critical(string $message, array $context = []) {
  157. $this->log(Util::ERROR, $message, $context);
  158. }
  159. /**
  160. * Runtime errors that do not require immediate action but should typically
  161. * be logged and monitored.
  162. *
  163. * @param string $message
  164. * @param array $context
  165. * @return void
  166. */
  167. public function error(string $message, array $context = []) {
  168. $this->log(Util::ERROR, $message, $context);
  169. }
  170. /**
  171. * Exceptional occurrences that are not errors.
  172. *
  173. * Example: Use of deprecated APIs, poor use of an API, undesirable things
  174. * that are not necessarily wrong.
  175. *
  176. * @param string $message
  177. * @param array $context
  178. * @return void
  179. */
  180. public function warning(string $message, array $context = []) {
  181. $this->log(Util::WARN, $message, $context);
  182. }
  183. /**
  184. * Normal but significant events.
  185. *
  186. * @param string $message
  187. * @param array $context
  188. * @return void
  189. */
  190. public function notice(string $message, array $context = []) {
  191. $this->log(Util::INFO, $message, $context);
  192. }
  193. /**
  194. * Interesting events.
  195. *
  196. * Example: User logs in, SQL logs.
  197. *
  198. * @param string $message
  199. * @param array $context
  200. * @return void
  201. */
  202. public function info(string $message, array $context = []) {
  203. $this->log(Util::INFO, $message, $context);
  204. }
  205. /**
  206. * Detailed debug information.
  207. *
  208. * @param string $message
  209. * @param array $context
  210. * @return void
  211. */
  212. public function debug(string $message, array $context = []) {
  213. $this->log(Util::DEBUG, $message, $context);
  214. }
  215. /**
  216. * Logs with an arbitrary level.
  217. *
  218. * @param int $level
  219. * @param string $message
  220. * @param array $context
  221. * @return void
  222. */
  223. public function log(int $level, string $message, array $context = []) {
  224. $minLevel = min($this->config->getValue('loglevel', Util::WARN), Util::FATAL);
  225. $logCondition = $this->config->getValue('log.condition', []);
  226. array_walk($context, [$this->normalizer, 'format']);
  227. if (isset($context['app'])) {
  228. $app = $context['app'];
  229. /**
  230. * check log condition based on the context of each log message
  231. * once this is met -> change the required log level to debug
  232. */
  233. if(!empty($logCondition)
  234. && isset($logCondition['apps'])
  235. && in_array($app, $logCondition['apps'], true)) {
  236. $minLevel = Util::DEBUG;
  237. }
  238. } else {
  239. $app = 'no app in context';
  240. }
  241. // interpolate $message as defined in PSR-3
  242. $replace = [];
  243. foreach ($context as $key => $val) {
  244. $replace['{' . $key . '}'] = $val;
  245. }
  246. // interpolate replacement values into the message and return
  247. $message = strtr($message, $replace);
  248. /**
  249. * check for a special log condition - this enables an increased log on
  250. * a per request/user base
  251. */
  252. if($this->logConditionSatisfied === null) {
  253. // default to false to just process this once per request
  254. $this->logConditionSatisfied = false;
  255. if(!empty($logCondition)) {
  256. // check for secret token in the request
  257. if(isset($logCondition['shared_secret'])) {
  258. $request = \OC::$server->getRequest();
  259. // if token is found in the request change set the log condition to satisfied
  260. if($request && hash_equals($logCondition['shared_secret'], $request->getParam('log_secret', ''))) {
  261. $this->logConditionSatisfied = true;
  262. }
  263. }
  264. // check for user
  265. if(isset($logCondition['users'])) {
  266. $user = \OC::$server->getUserSession()->getUser();
  267. // if the user matches set the log condition to satisfied
  268. if($user !== null && in_array($user->getUID(), $logCondition['users'], true)) {
  269. $this->logConditionSatisfied = true;
  270. }
  271. }
  272. }
  273. }
  274. // if log condition is satisfied change the required log level to DEBUG
  275. if($this->logConditionSatisfied) {
  276. $minLevel = Util::DEBUG;
  277. }
  278. if ($level >= $minLevel) {
  279. $logger = $this->logger;
  280. call_user_func([$logger, 'write'], $app, $message, $level);
  281. }
  282. }
  283. /**
  284. * Logs an exception very detailed
  285. *
  286. * @param \Exception|\Throwable $exception
  287. * @param array $context
  288. * @return void
  289. * @since 8.2.0
  290. */
  291. public function logException(\Throwable $exception, array $context = []) {
  292. $level = Util::ERROR;
  293. if (isset($context['level'])) {
  294. $level = $context['level'];
  295. unset($context['level']);
  296. }
  297. $data = [
  298. 'Exception' => get_class($exception),
  299. 'Message' => $exception->getMessage(),
  300. 'Code' => $exception->getCode(),
  301. 'Trace' => $exception->getTraceAsString(),
  302. 'File' => $exception->getFile(),
  303. 'Line' => $exception->getLine(),
  304. ];
  305. $data['Trace'] = preg_replace('!(' . implode('|', $this->methodsWithSensitiveParameters) . ')\(.*\)!', '$1(*** sensitive parameters replaced ***)', $data['Trace']);
  306. if ($exception instanceof HintException) {
  307. $data['Hint'] = $exception->getHint();
  308. }
  309. $msg = isset($context['message']) ? $context['message'] : 'Exception';
  310. $msg .= ': ' . json_encode($data);
  311. $this->log($level, $msg, $context);
  312. $context['level'] = $level;
  313. if (!is_null($this->crashReporters)) {
  314. $this->crashReporters->delegateReport($exception, $context);
  315. }
  316. }
  317. /**
  318. * @param string $logType
  319. * @return string
  320. * @internal
  321. */
  322. public static function getLogClass(string $logType): string {
  323. switch (strtolower($logType)) {
  324. case 'errorlog':
  325. return \OC\Log\Errorlog::class;
  326. case 'syslog':
  327. return \OC\Log\Syslog::class;
  328. case 'file':
  329. return \OC\Log\File::class;
  330. // Backwards compatibility for old and fallback for unknown log types
  331. case 'owncloud':
  332. case 'nextcloud':
  333. default:
  334. return \OC\Log\File::class;
  335. }
  336. }
  337. }