Using log4php
A how-to for the excellent log4php:
- Rolling daily file
- Log error statements into one file, and info statements into another
- Send emails
Usage
include_once('/var/www/example/components/log4php/2.0.0/Logger.php');
class Example
{
private $logger;
function __construct()
{
$loggerConfig = '/var/www/example/config/log4php.xml';
Logger::configure($loggerConfig);
$this->logger = Logger::getLogger('example');
}
public function doSomething()
{
$var = $this->getSomeValue();
$this->logger->debug('in doSomething: $var = ' . $var);
try
{
$this->doSomethingSpecific();
}
catch (Exception $ex)
{
$this->logger->error($ex->getMessage());
throw new Exception("There is a problem with Something. We apologize for any inconvenience caused.");
}
$this->logger->info("doSomething successful");
}
}
log4php.xml
<log4php:configuration xmlns:log4php="http://logging.apache.org/log4php/">
<appender name="default" class="LoggerAppenderDailyFile">
<param name="file" value="/var/www/example/logs/%s.log" />
<param name="datePattern" value="Ymd" />
<layout class="LoggerLayoutPattern">
<param name="ConversionPattern" value="%d{Y-m-d H:i:s} [%p] %c: %m (at %F line %L)%n" />
</layout>
</appender>
<appender name="exampleDebugLog" class="LoggerAppenderDailyFile">
<param name="file" value="/var/www/example/logs/debug.%s.log" />
<param name="datePattern" value="Ymd" />
<layout class="LoggerLayoutPattern">
<param name="ConversionPattern" value="%d{Y-m-d H:i:s} [%p] %c: %m%n" />
</layout>
<filter class="LoggerFilterLevelRange">
<param name="LevelMin" value="debug" />
<param name="LevelMax" value="debug" />
</filter>
<filter class="LoggerFilterDenyAll" />
</appender>
<appender name="exampleInfoLog" class="LoggerAppenderDailyFile">
<param name="file" value="/var/www/example/logs/info.%s.log" />
<param name="datePattern" value="Ymd" />
<layout class="LoggerLayoutPattern">
<param name="ConversionPattern" value="%d{Y-m-d H:i:s} [%p] %c: %m%n" />
</layout>
<filter class="LoggerFilterLevelRange">
<param name="LevelMin" value="info" />
<param name="LevelMax" value="info" />
</filter>
<filter class="LoggerFilterDenyAll" />
</appender>
<appender name="exampleErrorLog" class="LoggerAppenderDailyFile">
<param name="file" value="/var/www/example/logs/errors.%s.log" />
<param name="datePattern" value="Ymd" />
<layout class="LoggerLayoutPattern">
<param name="ConversionPattern" value="%d{Y-m-d H:i:s} [%p] %c: %m (at %F line %L)%n" />
</layout>
<filter class="LoggerFilterLevelRange">
<param name="LevelMin" value="error" />
<param name="LevelMax" value="fatal" />
</filter>
<filter class="LoggerFilterDenyAll" />
</appender>
<appender name="exampleErrorEmail" class="LoggerAppenderMail">
<param name="from" value="sys@example.com" />
<param name="to" value="support@example.com" />
<param name="subject" value="Error" />
<layout class="LoggerLayoutPattern">
<param name="ConversionPattern" value="%d{Y-m-d H:i:s} [%p] %c: %m (at %F line %L)%n" />
</layout>
<filter class="LoggerFilterLevelRange">
<param name="LevelMin" value="error" />
<param name="LevelMax" value="fatal" />
</filter>
<filter class="LoggerFilterDenyAll" />
</appender>
<root>
<level value="DEBUG" />
<appender_ref ref="default" />
</root>
<logger name="example" additivity="false">
<level value="INFO" />
<appender_ref ref="exampleInfoLog" />
<appender_ref ref="exampleErrorLog" />
<appender_ref ref="exampleErrorEmail" />
</logger>
</log4php:configuration>
Calling a .NET asmx web service over https from PHP
- Enable SOAP by uncommenting “extension=php_soap.dll” in php.ini
- https should be included in “Registered PHP Streams” (phpinfo) – uncomment “extension=php_openssl.dll” in php.ini
// SOAP client
$wsdl = 'https://www.example.com/services/ProductManagement.asmx?wsdl';
$endpoint = array
(
'location' => 'https://www.example.com/services/ProductManagement.asmx'
);
$soapClient = new SoapClient($wsdl, $endpoint);
// SOAP header
$soapHeaders = array();
$namespace = 'http://www.example.com/services/ProductManagement';
$elementName = 'Authentication';
$authenticationHeader = array
(
'Username' => 'user9',
'Password' => 'pass1234',
);
$soapHeader = new SoapHeader($namespace, $elementName, $authenticationHeader);
$soapHeaders[] = $soapHeader;
$soapClient->__setSoapHeaders($soapHeaders);
// SOAP call
$parameters->productCode = '9555170408350';
try
{
$result = $soapClient->GetProduct($parameters);
}
catch (SoapFault $fault)
{
echo "Fault code: {$fault->faultcode}<br />\n";
echo "Fault string: {$fault->faultstring}<br />\n";
if ($soapClient != null)
{
$soapClient = null;
}
exit();
}
$soapClient = null;
//echo "<pre>\n";
//print_r($result);
//echo "</pre>\n";
echo "Product code: {$result->GetProductResult->ProductCode}<br />\n";
echo "Name: {$result->GetProductResult->Name}<br />\n";
echo "Description: {$result->GetProductResult->Description}<br />\n";
echo "Category: {$result->GetProductResult->Category}<br />\n";
echo "SKU: {$result->GetProductResult->SKU}<br />\n";
echo "Manufacturer: {$result->GetProductResult->Manufacturer}<br />\n" ;
echo "Brand: {$result->GetProductResult->Brand}<br />\n";
echo "In stock: ";
echo ($result->GetProductResult->IsInStock ? "Yes" : "No") . "<br />\n";
Data Sanitizer
I wrote a C# console app to sanitize CSV files. It replaces numbers with X’s so that social security number 123456 becomes XXXXXX and the address “49 Main St” becomes “XX Main St”. Both the input and the output are streamed to ensure that the program won’t run out of memory even with arbitrarily large files.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace DataSanitizer
{
class Program
{
private const string Delimiter = "|";
private const string ReplacementCharacter = "X";
private const int NumFields = 15;
static void Main(string[] args)
{
if (args.Length < 2)
{
Console.WriteLine("Usage: DataSanitizer.exe input_file output_file");
return;
}
string inputFilePathAndName = args[0];
string outputFilePathAndName = args[1];
using (StreamReader sr = new StreamReader(inputFilePathAndName))
using (StreamWriter sw = new StreamWriter(outputFilePathAndName, true))
{
string line;
while ((line = sr.ReadLine()) != null)
{
string[] fields = line.Split(Delimiter.ToCharArray());
string ssn = fields[2];
string addressLine1 = fields[4];
string addressLine2 = fields[5];
string addressLine3 = fields[6];
string telHome = fields[9];
string telOffice = fields[10];
string telMobile = fields[11];
Regex alphanumeric = new Regex(@"\w");
Regex numeric = new Regex(@"\d");
string ssnSanitized = alphanumeric.Replace(ssn, ReplacementCharacter);
string addressLine1Sanitized = numeric.Replace(addressLine1, ReplacementCharacter);
string addressLine2Sanitized = numeric.Replace(addressLine2, ReplacementCharacter);
string addressLine3Sanitized = numeric.Replace(addressLine3, ReplacementCharacter);
string telHomeSanitized = alphanumeric.Replace(telHome, ReplacementCharacter);
string telOfficeSanitized = alphanumeric.Replace(telOffice, ReplacementCharacter);
string telMobileSanitized = alphanumeric.Replace(telMobile, ReplacementCharacter);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < NumFields; i++)
{
if (i > 0)
{
sb.Append(Delimiter);
}
switch (i)
{
case 2:
sb.Append(ssnSanitized);
break;
case 4:
sb.Append(addressLine1Sanitized);
break;
case 5:
sb.Append(addressLine2Sanitized);
break;
case 6:
sb.Append(addressLine3Sanitized);
break;
case 9:
sb.Append(telHomeSanitized);
break;
case 10:
sb.Append(telOfficeSanitized);
break;
case 11:
sb.Append(telMobileSanitized);
break;
default:
sb.Append(fields[i]);
break;
}
}
sw.WriteLine(sb.ToString());
}
}
}
}
}
Urgent
You don’t know the meaning of urgent until you’re stuck in a traffic jam with a small child who has just started potty training, who urgently needs to go …
App feature request
I wish that desktop apps would have this feature: when I attempt to close it, and it asks, “Do you want to save the changes you made to <filename>?”, there’s an extra button that says “View unsaved changes”.