Commit 35354886 authored by Armando Lüscher's avatar Armando Lüscher

Merge pull request #200 from MBoretto/monolog

Adventures with Monolog, ready for the real world 👌
parents 7ec5489f 4b3019ff
......@@ -43,7 +43,7 @@ A Telegram Bot based on the official [Telegram Bot API](https://core.telegram.or
- [Set Admins](#set-admins)
- [Channel Administration](#channel-administration)
- [Upload and Download directory path](#upload-and-download-directory-path)
- [Logging](#logging)
- [Logging](doc/01-utils.md)
- [Documentation](#documentation)
- [Projects with this library](#projects-with-this-library)
- [Troubleshooting](#troubleshooting)
......@@ -511,27 +511,6 @@ $telegram->setDownloadPath('yourpath/Download');
$telegram->setUploadPath('yourpath/Upload');
```
### Logging
Thrown Exceptions are not stored by default. You can Enable this feature adding this line in your 'webhook.php' or 'getUpdates.php'
```php
Longman\TelegramBot\Logger::initialize('your_path/TelegramException.log');
```
Incoming update (json string from webhook and getUpdates) can be logged in a text file. Set those options with the methods:
```php
$telegram->setLogRequests(true);
$telegram->setLogPath($BOT_NAME . '.log');
```
Set verbosity to 3 to also log curl requests and responses from the bot to Telegram:
```php
$telegram->setLogRequests(true);
$telegram->setLogPath($BOT_NAME . '.log');
$telegram->setLogVerbosity(3);
```
## Documentation
Take a look at the repo [Wiki](https://github.com/akalongman/php-telegram-bot/wiki) for further information and tutorials!
......
......@@ -20,7 +20,8 @@
"require": {
"php": ">=5.5.0",
"ext-pdo": "*",
"ext-curl": "*"
"ext-curl": "*",
"monolog/monolog": "^1.19"
},
"autoload": {
"psr-4": {
......
This diff is collapsed.
## Logging
Telegram bot library feats [Monolog](https://github.com/Seldaek/monolog) to store logs.
Logs are divided in those streams:
### Error
Collects all the exceptions throwned by the library:
```php
TelegramLog::initErrorLog($path . '/' . $BOT_NAME . '_error.log');
```
### Debug
Stores Curl messages with the server, useful for debugging:
```php
TelegramLog::initDebugLog($path . '/' . $BOT_NAME . '_debug.log');
```
### Raw data
Incoming updates (json string from webhook and getUpdates) can be logged in a text file. Set this option with the methods:
```php
TelegramLog::initUpdateLog($path . '/' . $BOT_NAME . '_update.log');
```
Why I need raw log?
Telegram api changes continuously and often happen that db schema is not uptodate with new entities/features. So can happen that your table schema would not be able to store valuable new information coming from Telegram.
If you store raw data you can port all updates on the newest table schema just using [this script](../utils/importFromLog.php).
Remember always backup first!!
## Stream and external sources
Error and Debug streams relies on the `bot_log` instance that can be provided from an external source:
```php
TelegramLog::initialize($monolog);
```
Raw data relies on the `bot_update_log` instance that feats a custom format for this kind of logs.
......@@ -4,8 +4,8 @@
//This configuration file is intented to run the bot with the webhook method
//Uncommented parameters must be filled
#bash script
#while true; do ./getUpdatesCLI.php; done
//bash script
//while true; do ./getUpdatesCLI.php; done
// Load composer
require __DIR__ . '/vendor/autoload.php';
......@@ -42,9 +42,10 @@ try {
//$telegram->setCommandConfig('date', ['google_api_key' => 'your_google_api_key_here']);
//// Logging
//$telegram->setLogRequests(true);
//$telegram->setLogPath($BOT_NAME . '.log');
//$telegram->setLogVerbosity(3);
//\Longman\TelegramBot\TelegramLog::initialize($your_external_monolog_instance);
//\Longman\TelegramBot\TelegramLog::initErrorLog($path . '/' . $BOT_NAME . '_error.log');
//\Longman\TelegramBot\TelegramLog::initDebugLog($path . '/' . $BOT_NAME . '_debug.log');
//\Longman\TelegramBot\TelegramLog::initUpdateLog($path . '/' . $BOT_NAME . '_update.log');
//// Set custom Upload and Download path
//$telegram->setDownloadPath('../Download');
......@@ -64,6 +65,10 @@ try {
echo $ServerResponse->printError() . "\n";
}
} catch (Longman\TelegramBot\Exception\TelegramException $e) {
echo $e;
// log telegram errors
\Longman\TelegramBot\TelegramLog::error($e);
} catch (Longman\TelegramBot\Exception\TelegramLogException $e) {
//catch log initilization errors
echo $e;
}
......@@ -3,7 +3,7 @@
//This configuration file is intended to run the bot with the webhook method.
//Uncommented parameters must be filled
//Please notice that if you open this file with your browser you'll get the "Input is empty!" Exception.
//This is a normal behaviour because this address has to be reached only by Telegram server
//This is a normal behaviour because this address has to be reached only by Telegram server.
// Load composer
require __DIR__ . '/vendor/autoload.php';
......@@ -40,9 +40,10 @@ try {
//$telegram->setCommandConfig('date', ['google_api_key' => 'your_google_api_key_here']);
//// Logging
//$telegram->setLogRequests(true);
//$telegram->setLogPath($BOT_NAME . '.log');
//$telegram->setLogVerbosity(3);
//\Longman\TelegramBot\TelegramLog::initialize($your_external_monolog_instance);
//\Longman\TelegramBot\TelegramLog::initErrorLog($path . '/' . $BOT_NAME . '_error.log');
//\Longman\TelegramBot\TelegramLog::initDebugLog($path . '/' . $BOT_NAME . '_debug.log');
//\Longman\TelegramBot\TelegramLog::initUpdateLog($path . '/' . $BOT_NAME . '_update.log');
//// Set custom Upload and Download path
//$telegram->setDownloadPath('../Download');
......@@ -54,7 +55,11 @@ try {
// Handle telegram webhook request
$telegram->handle();
} catch (Longman\TelegramBot\Exception\TelegramException $e) {
// Silence is golden!
// log telegram errors
// Silence is gold!
// echo $e;
// log telegram errors
\Longman\TelegramBot\TelegramLog::error($e);
} catch (Longman\TelegramBot\Exception\TelegramLogException $e) {
// Silence is gold! Uncomment this to catch log initilization errors
//echo $e;
}
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
......@@ -10,22 +10,9 @@
namespace Longman\TelegramBot\Exception;
use Longman\TelegramBot\Logger;
/**
* Main exception class used for exception handling
*/
class TelegramException extends \Exception
{
/**
* Exception constructor that writes the exception message to the logfile
*
* @param string $message Error message
* @param integer $code Error code
*/
public function __construct($message, $code = 0)
{
parent::__construct($message, $code);
Logger::logException(self::__toString());
}
}
<?php
/**
* This file is part of the TelegramBot package.
*
* (c) Avtandil Kikabidze aka LONGMAN <akalongman@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Longman\TelegramBot\Exception;
/**
* Main exception class used for exception handling
*/
class TelegramLogException extends \Exception
{
}
<?php
/**
* This file is part of the TelegramBot package.
*
* (c) Avtandil Kikabidze aka LONGMAN <akalongman@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Longman\TelegramBot;
/**
* Class Logger.
*/
class Logger
{
/**
* Exception log path
*
* @var string
*/
static protected $exception_log_path = null;
/**
* Initialize
*
* @param string $exception_log_path
*/
public static function initialize($exception_log_path)
{
self::$exception_log_path = $exception_log_path;
}
/**
* Log exception
*
* @param string $text
*
* @return bool
*/
public static function logException($text)
{
if (!is_null(self::$exception_log_path)) {
return file_put_contents(
self::$exception_log_path,
date('Y-m-d H:i:s', time()) . ' ' . $text . "\n",
FILE_APPEND
);
}
return 0;
}
}
......@@ -95,6 +95,7 @@ class Request
{
if (is_string($input) | $input == false) {
self::$input = $input;
TelegramLog::update(self::$input);
} else {
throw new TelegramException('Input must be a string!');
}
......@@ -112,33 +113,9 @@ class Request
} else {
self::setInputRaw(file_get_contents('php://input'));
}
self::log(self::$input);
return self::$input;
}
/**
* Write log entry
*
* @todo Take log verbosity into account
*
* @param string $string
*
* @return mixed
*/
private static function log($string)
{
if (!self::$telegram->getLogRequests()) {
return false;
}
$path = self::$telegram->getLogPath();
if (!$path) {
return false;
}
return file_put_contents($path, $string . "\n", FILE_APPEND);
}
/**
* Generate general fake server response
*
......@@ -203,34 +180,36 @@ class Request
$curlConfig[CURLOPT_POSTFIELDS] = $data;
}
if (self::$telegram->getLogVerbosity() >= 3) {
if (TelegramLog::isDebugLogActive()) {
$verbose_curl_output = fopen('php://temp', 'w+');
$curlConfig[CURLOPT_VERBOSE] = true;
$verbose = fopen('php://temp', 'w+');
curl_setopt($ch, CURLOPT_STDERR, $verbose);
$curlConfig[CURLOPT_STDERR] = $verbose_curl_output;
}
curl_setopt_array($ch, $curlConfig);
$result = curl_exec($ch);
//Logging curl requests
if (self::$telegram->getLogVerbosity() >= 3) {
rewind($verbose);
$verboseLog = stream_get_contents($verbose);
self::log('Verbose curl output:' . "\n" . htmlspecialchars($verboseLog) . "\n");
if (TelegramLog::isDebugLogActive()) {
rewind($verbose_curl_output);
$verboseLog = stream_get_contents($verbose_curl_output);
fclose($verbose_curl_output);
TelegramLog::debug('Verbose curl output:' . "\n" . htmlspecialchars($verboseLog) . "\n");
}
//Logging getUpdates Update
//Logging curl updates
if ($action == 'getUpdates' & self::$telegram->getLogVerbosity() >= 1 | self::$telegram->getLogVerbosity() >= 3
) {
if ($action == 'getUpdates') {
//Will be Logged in Update steam
self::setInputRaw($result);
self::log($result);
}
$curl_error = curl_error($ch);
$curl_errno = curl_errno($ch);
curl_close($ch);
if ($result === false) {
throw new TelegramException(curl_error($ch), curl_errno($ch));
throw new TelegramException($curl_error, $curl_errno);
}
if (empty($result) | is_null($result)) {
throw new TelegramException('Empty server response');
......
......@@ -67,20 +67,6 @@ class Telegram
*/
protected $update;
/**
* Log verbose curl output
*
* @var bool
*/
protected $log_requests;
/**
* Log path
*
* @var string
*/
protected $log_path;
/**
* Upload path
*
......@@ -95,13 +81,6 @@ class Telegram
*/
protected $download_path;
/**
* Log verbosity
*
* @var int
*/
protected $log_verbosity = 1;
/**
* MySQL integration
*
......@@ -232,6 +211,7 @@ class Telegram
require_once $file->getPathname();
$command_obj = $this->getCommandObject($command);
if ($command_obj instanceof Commands\Command) {
$commands[$command_name] = $command_obj;
......@@ -268,81 +248,6 @@ class Telegram
return null;
}
/**
* Set log requests
*
* 0 don't store
* 1 store the Curl verbose output with Telegram updates
*
* @param bool $log_requests
*
* @return Telegram
*/
public function setLogRequests($log_requests)
{
$this->log_requests = $log_requests;
return $this;
}
/**
* Get log requests
*
* @return bool
*/
public function getLogRequests()
{
return $this->log_requests;
}
/**
* Set log path
*
* @param string $log_path
*
* @return \Longman\TelegramBot\Telegram
*/
public function setLogPath($log_path)
{
$this->log_path = $log_path;
return $this;
}
/**
* Get log path
*
* @return string
*/
public function getLogPath()
{
return $this->log_path;
}
/**
* Set log Verbosity
*
* @param int $log_verbosity
*
* 1 only incoming updates from webhook and getUpdates
* 3 incoming updates from webhook and getUpdates and curl request info and response
*
* @return \Longman\TelegramBot\Telegram
*/
public function setLogVerbosity($log_verbosity)
{
$this->log_verbosity = $log_verbosity;
return $this;
}
/**
* Get log verbosity
*
* @return int
*/
public function getLogVerbosity()
{
return $this->log_verbosity;
}
/**
* Set custom input string for debug purposes
*
......
<?php
/**
* This file is part of the TelegramBot package.
*
* (c) Avtandil Kikabidze aka LONGMAN <akalongman@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Longman\TelegramBot;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Formatter\LineFormatter;
use Longman\TelegramBot\Exception\TelegramLogException;
/**
* Class TelegramLog.
*/
class TelegramLog
{
/**
* Monolog instance
*
* @var \Monolog\Logger
*/
static protected $monolog = null;
/**
* Monolog instance for update
*
* @var \Monolog\Logger
*/
static protected $monolog_update = null;
/**
* Path for error log
*
* @var string
*/
static protected $error_log_path = null;
/**
* Path for debug log
*
* @var string
*/
static protected $debug_log_path = null;
/**
* Path for update log
*
* @var string
*/
static protected $update_log_path = null;
/**
* Initialize
*
* Initilize monolog instance. Singleton
* Is possbile provide an external monolog instance
*
* @param \Monolog\Logger
*
* @return \Monolog\Logger
*/
public static function initialize(Logger $external_monolog = null)
{
if (self::$monolog === null) {
if ($external_monolog !== null) {
self::$monolog = $external_monolog;
foreach (self::$monolog->getHandlers() as $handler) {
if ($handler->getLevel() == 400) {
self::$error_log_path = true;
}
if ($handler->getLevel() == 100) {
self::$debug_log_path = true;
}
}
} else {
self::$monolog = new Logger('bot_log');
}
}
return self::$monolog;
}
/**
* Initialize error log
*
* @param string $path
*
* @return \Monolog\Logger
*/
public static function initErrorLog($path)
{
if ($path === null || $path === '') {
throw new TelegramLogException('Empty path for error log');
}
self::initialize();
self::$error_log_path = $path;
return self::$monolog->pushHandler(new StreamHandler(self::$error_log_path, Logger::ERROR));
}
/**
* Initialize debug log
*
* @param string $path
*
* @return \Monolog\Logger
*/
public static function initDebugLog($path)
{
if ($path === null || $path === '') {
throw new TelegramLogException('Empty path for debug log');
}
self::initialize();
self::$debug_log_path = $path;
return self::$monolog->pushHandler(new StreamHandler(self::$debug_log_path, Logger::DEBUG));
}
/**
* Initialize update log
*
* Initilize monolog instance. Singleton
* Is possbile provide an external monolog instance
*
* @param string $path
*
* @return \Monolog\Logger
*/
public static function initUpdateLog($path)
{
if ($path === null || $path === '') {
throw new TelegramLogException('Empty path for update log');
}
self::$update_log_path = $path;
if (self::$monolog_update === null) {
self::$monolog_update = new Logger('bot_update_log');
// Create a formatter
$output = "%message%\n";
$formatter = new LineFormatter($output);
// Update handler
$update_handler = new StreamHandler(self::$update_log_path, Logger::INFO);
$update_handler->setFormatter($formatter);
self::$monolog_update->pushHandler($update_handler);
}
return self::$monolog;
}
/**
* Is error log active
*
* @return bool
*/
public static function isErrorLogActive()
{
return (self::$error_log_path !== null);
}
/**
* Is debug log active
*
* @return bool
*/
public static function isDebugLogActive()
{
return (self::$debug_log_path !== null);
}
/**
* Is update log active
*
* @return bool
*/
public static function isUpdateLogActive()
{
return (self::$update_log_path !== null);
}
/**
* Report error log
*
* @param string $text
*/
public static function error($text)
{
if (self::isErrorLogActive()) {
self::$monolog->error($text);
}
}
/**
* Report debug log
*
* @param string $text
*/
public static function debug($text)
{
if (self::isDebugLogActive()) {
self::$monolog->debug($text);
}
}
/**
* Report update log
*
* @param string $text
*/
public static function update($text)
{
if (self::isUpdateLogActive()) {
self::$monolog_update->info($text);
}
}
}
......@@ -53,7 +53,7 @@ class TestHelpers
/**
* Set the value of a private/protected property of an object
*
* @param object $object Object that contains the private property
* @param object $object Object that contains the property
* @param string $property Name of the property who's value we want to set
* @param mixed $value The value to set to the property
*/
......@@ -65,6 +65,20 @@ class TestHelpers
$ref_property->setValue($object, $value);
}
/**
* Set the value of a private/protected static property of a class
*
* @param string $class Class that contains the static property
* @param string $property Name of the property who's value we want to set
* @param mixed $value The value to set to the property
*/
public static function setStaticProperty($class, $property, $value)
{
$ref_property = new \ReflectionProperty($class, $property);
$ref_property->setAccessible(true);
$ref_property->setValue(null, $value);
}
/**
* Return a simple fake Update object
*
......
......@@ -22,26 +22,22 @@ use \Longman\TelegramBot\Entities\Message;
class MessageTest extends TestCase
{
/**
* @var \Longman\TelegramBot\Telegram
*/
* @var \Longman\TelegramBot\Telegram
*/
private $message;
/**
* setUp
*/
* setUp
*/
protected function setUp()
{
}
protected function generateMessage($string) {
//$string = addslashes($string);
$string = str_replace("\n", "\\n", $string);
$json = '{"message_id":961,"from":{"id":123,"first_name":"john","username":"john"},"chat":{"id":123,"title":null,"first_name":"john","last_name":null,"username":"null"},"date":1435920612,"text":"'.$string.'"}';
//$json = utf8_encode($json);
//$json = utf8_encode($json);
return json_decode($json, true);
}
/**
......@@ -56,8 +52,8 @@ class MessageTest extends TestCase
$this->assertEquals('help', $this->message->getCommand());
$this->assertEquals('/help', $this->message->getText());
$this->assertEquals('', $this->message->getText(true));
// text
// text
$this->message = new Message($this->generateMessage('some text'), 'testbot');
$this->assertEquals('', $this->message->getFullCommand());
......
<?php
/**
* This file is part of the TelegramBot package.
*
* (c) Avtandil Kikabidze aka LONGMAN <akalongman@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tests\Unit;
use Longman\TelegramBot\Exception\TelegramLogException;
use Longman\TelegramBot\TelegramLog;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
use Tests\TestHelpers;
/**
* @package TelegramTest
* @author Avtandil Kikabidze <akalongman@gmail.com>
* @copyright Avtandil Kikabidze <akalongman@gmail.com>
* @license http://opensource.org/licenses/mit-license.php The MIT License (MIT)
* @link http://www.github.com/akalongman/php-telegram-bot
*/
class TelegramLogTest extends TestCase
{
/**
* Logfile paths
*/
private $logfiles = [
'error' => '/tmp/errorlog.log',
'debug' => '/tmp/debuglog.log',
'update' => '/tmp/updatelog.log',
'external' => '/tmp/externallog.log',
];
/**
* setUp
*/
protected function setUp()
{
// Make sure no monolog instance is set before each test.
TestHelpers::setStaticProperty('Longman\TelegramBot\TelegramLog', 'monolog', null);
}
/**
* tearDown
*/
protected function tearDown()
{
// Make sure no logfiles exist.
foreach ($this->logfiles as $file) {
file_exists($file) && unlink($file);
}
}
/**
* @test
* @expectedException \Longman\TelegramBot\Exception\TelegramLogException
*/
public function newInstanceWithoutErrorPath()
{
TelegramLog::initErrorLog('');
}
/**
* @test
* @expectedException \Longman\TelegramBot\Exception\TelegramLogException
*/
public function newInstanceWithoutDebugPath()
{
TelegramLog::initDebugLog('');
}
/**
* @test
* @expectedException \Longman\TelegramBot\Exception\TelegramLogException
*/
public function newInstanceWithoutUpdatePath()
{
TelegramLog::initUpdateLog('');
}
/**
* @test
*/
public function testErrorStream()
{
$file = $this->logfiles['error'];
$this->assertFalse(file_exists($file));
TelegramLog::initErrorLog($file);
TelegramLog::error('my error');
$this->assertTrue(file_exists($file));
$this->assertContains('bot_log.ERROR: my error', file_get_contents($file));
}
/**
* @test
*/
public function testDebugStream()
{
$file = $this->logfiles['debug'];
$this->assertFalse(file_exists($file));
TelegramLog::initDebugLog($file);
TelegramLog::debug('my debug');
$this->assertTrue(file_exists($file));
$this->assertContains('bot_log.DEBUG: my debug', file_get_contents($file));
}
/**
* @test
*/
public function testUpdateStream()
{
$file = $this->logfiles['update'];
$this->assertFalse(file_exists($file));
TelegramLog::initUpdateLog($file);
TelegramLog::update('my update');
$this->assertTrue(file_exists($file));
$this->assertContains('my update', file_get_contents($file));
}
/**
* @test
*/
public function testExternalStream()
{
$file = $this->logfiles['external'];
$this->assertFalse(file_exists($file));
$external_monolog = new Logger('bot_update_log');
$external_monolog->pushHandler(new StreamHandler($file, Logger::ERROR));
$external_monolog->pushHandler(new StreamHandler($file, Logger::DEBUG));
TelegramLog::initialize($external_monolog);
TelegramLog::error('my error');
TelegramLog::debug('my debug');
$this->assertTrue(file_exists($file));
$file_contents = file_get_contents($file);
$this->assertContains('bot_update_log.ERROR: my error', $file_contents);
$this->assertContains('bot_update_log.DEBUG: my debug', $file_contents);
}
}
<?php
require __DIR__ . '/../vendor/autoload.php';
$filename='logfile.log';
$API_KEY = 'random';
$BOT_NAME = 'bot_name';
define('PHPUNIT_TESTSUITE', 'some value');
$CREDENTIALS = array('host'=>'localhost', 'user'=>'', 'password'=>'', 'database'=>'');
$update = null;
try {
// Create Telegram API object
$telegram = new Longman\TelegramBot\Telegram($API_KEY, $BOT_NAME);
$telegram->enableMySQL($CREDENTIALS);
foreach (new SplFileObject($filename) as $current_line) {
$json_decoded = json_decode($update, true);
if (!is_null($json_decoded)) {
echo $update . "\n\n";
$update = null;
if (empty($json_decoded)) {
echo "Empty update: \n";
echo $update . "\n\n";
continue;
}
$telegram->processUpdate(new Longman\TelegramBot\Entities\Update($json_decoded, $BOT_NAME));
}
$update .= $current_line;
}
} catch (Longman\TelegramBot\Exception\TelegramException $e) {
// log telegram errors
echo $e;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment