/usr/share/perl5/Log/Log4perl/FAQ.pm is in liblog-log4perl-perl 1.41-1.1ubuntu1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
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 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 | 1;
__END__
=head1 NAME
Log::Log4perl::FAQ - Frequently Asked Questions on Log::Log4perl
=head1 DESCRIPTION
This FAQ shows a wide variety of
commonly encountered logging tasks and how to solve them
in the most elegant way with Log::Log4perl. Most of the time, this will
be just a matter of smartly configuring your Log::Log4perl configuration files.
=head2 Why use Log::Log4perl instead of any other logging module on CPAN?
That's a good question. There's dozens of logging modules on CPAN.
When it comes to logging, people typically think: "Aha. Writing out
debug and error messages. Debug is lower than error. Easy. I'm gonna
write my own." Writing a logging module is like a rite of passage for
every Perl programmer, just like writing your own templating system.
Of course, after getting the basics right, features need to
be added. You'd like to write a timestamp with every message. Then
timestamps with microseconds. Then messages need to be written to both
the screen and a log file.
And, as your application grows in size you might wonder: Why doesn't
my logging system scale along with it? You would like to switch on
logging in selected parts of the application, and not all across the
board, because this kills performance. This is when people turn to
Log::Log4perl, because it handles all of that.
Avoid this costly switch.
Use C<Log::Log4perl> right from the start. C<Log::Log4perl>'s C<:easy>
mode supports easy logging in simple scripts:
use Log::Log4perl qw(:easy);
Log::Log4perl->easy_init($DEBUG);
DEBUG "A low-level message";
ERROR "Won't make it until level gets increased to ERROR";
And when your application inevitably grows, your logging system grows
with it without you having to change any code.
Please, don't re-invent logging. C<Log::Log4perl> is here, it's easy
to use, it scales, and covers many areas you haven't thought of yet,
but will enter soon.
=head2 What's the easiest way to use Log4perl?
If you just want to get all the comfort of logging, without much
overhead, use I<Stealth Loggers>. If you use Log::Log4perl in
C<:easy> mode like
use Log::Log4perl qw(:easy);
you'll have the following functions available in the current package:
DEBUG("message");
INFO("message");
WARN("message");
ERROR("message");
FATAL("message");
Just make sure that every package of your code where you're using them in
pulls in C<use Log::Log4perl qw(:easy)> first, then you're set.
Every stealth logger's category will be equivalent to the name of the
package it's located in.
These stealth loggers
will be absolutely silent until you initialize Log::Log4perl in
your main program with either
# Define any Log4perl behavior
Log::Log4perl->init("foo.conf");
(using a full-blown Log4perl config file) or the super-easy method
# Just log to STDERR
Log::Log4perl->easy_init($DEBUG);
or the parameter-style method with a complexity somewhat in between:
# Append to a log file
Log::Log4perl->easy_init( { level => $DEBUG,
file => ">>test.log" } );
For more info, please check out L<Log::Log4perl/"Stealth Loggers">.
=head2 How can I simply log all my ERROR messages to a file?
After pulling in the C<Log::Log4perl> module, just initialize its
behavior by passing in a configuration to its C<init> method as a string
reference. Then, obtain a logger instance and write out a message
with its C<error()> method:
use Log::Log4perl qw(get_logger);
# Define configuration
my $conf = q(
log4perl.logger = ERROR, FileApp
log4perl.appender.FileApp = Log::Log4perl::Appender::File
log4perl.appender.FileApp.filename = test.log
log4perl.appender.FileApp.layout = PatternLayout
log4perl.appender.FileApp.layout.ConversionPattern = %d> %m%n
);
# Initialize logging behavior
Log::Log4perl->init( \$conf );
# Obtain a logger instance
my $logger = get_logger("Bar::Twix");
$logger->error("Oh my, a dreadful error!");
$logger->warn("Oh my, a dreadful warning!");
This will append something like
2002/10/29 20:11:55> Oh my, a dreadful error!
to the log file C<test.log>. How does this all work?
While the Log::Log4perl C<init()> method typically
takes the name of a configuration file as its input parameter like
in
Log::Log4perl->init( "/path/mylog.conf" );
the example above shows how to pass in a configuration as text in a
scalar reference.
The configuration as shown
defines a logger of the root category, which has an appender of type
C<Log::Log4perl::Appender::File> attached. The line
log4perl.logger = ERROR, FileApp
doesn't list a category, defining a root logger. Compare that with
log4perl.logger.Bar.Twix = ERROR, FileApp
which would define a logger for the category C<Bar::Twix>,
showing probably different behavior. C<FileApp> on
the right side of the assignment is
an arbitrarily defined variable name, which is only used to somehow
reference an appender defined later on.
Appender settings in the configuration are defined as follows:
log4perl.appender.FileApp = Log::Log4perl::Appender::File
log4perl.appender.FileApp.filename = test.log
It selects the file appender of the C<Log::Log4perl::Appender>
hierarchy, which will append to the file C<test.log> if it already
exists. If we wanted to overwrite a potentially existing file, we would
have to explicitly set the appropriate C<Log::Log4perl::Appender::File>
parameter C<mode>:
log4perl.appender.FileApp = Log::Log4perl::Appender::File
log4perl.appender.FileApp.filename = test.log
log4perl.appender.FileApp.mode = write
Also, the configuration defines a PatternLayout format, adding
the nicely formatted current date and time, an arrow (E<gt>) and
a space before the messages, which is then followed by a newline:
log4perl.appender.FileApp.layout = PatternLayout
log4perl.appender.FileApp.layout.ConversionPattern = %d> %m%n
Obtaining a logger instance and actually logging something is typically
done in a different system part as the Log::Log4perl initialisation section,
but in this example, it's just done right after init for the
sake of compactness:
# Obtain a logger instance
my $logger = get_logger("Bar::Twix");
$logger->error("Oh my, a dreadful error!");
This retrieves an instance of the logger of the category C<Bar::Twix>,
which, as all other categories, inherits behavior from the root logger if no
other loggers are defined in the initialization section.
The C<error()>
method fires up a message, which the root logger catches. Its
priority is equal to
or higher than the root logger's priority (ERROR), which causes the root logger
to forward it to its attached appender. By contrast, the following
$logger->warn("Oh my, a dreadful warning!");
doesn't make it through, because the root logger sports a higher setting
(ERROR and up) than the WARN priority of the message.
=head2 How can I install Log::Log4perl on Microsoft Windows?
You can install Log::Log4perl using the CPAN client.
Alternatively you can install it using
ppm install Log-Log4perl
if you're using ActiveState perl.
That's it! Afterwards, just create a Perl script like
use Log::Log4perl qw(:easy);
Log::Log4perl->easy_init($DEBUG);
my $logger = get_logger("Twix::Bar");
$logger->debug("Watch me!");
and run it. It should print something like
2002/11/06 01:22:05 Watch me!
If you find that something doesn't work, please let us know at
log4perl-devel@lists.sourceforge.net -- we'll appreciate it. Have fun!
=head2 How can I include global (thread-specific) data in my log messages?
Say, you're writing a web application and want all your
log messages to include the current client's IP address. Most certainly,
you don't want to include it in each and every log message like in
$logger->debug( $r->connection->remote_ip,
" Retrieving user data from DB" );
do you? Instead, you want to set it in a global data structure and
have Log::Log4perl include it automatically via a PatternLayout setting
in the configuration file:
log4perl.appender.FileApp.layout.ConversionPattern = %X{ip} %m%n
The conversion specifier C<%X{ip}> references an entry under the key
C<ip> in the global C<MDC> (mapped diagnostic context) table, which
you've set once via
Log::Log4perl::MDC->put("ip", $r->connection->remote_ip);
at the start of the request handler. Note that this is a
I<static> (class) method, there's no logger object involved.
You can use this method with as many key/value pairs as you like as long
as you reference them under different names.
The mappings are stored in a global hash table within Log::Log4perl.
Luckily, because the thread
model in 5.8.0 doesn't share global variables between threads unless
they're explicitly marked as such, there's no problem with multi-threaded
environments.
For more details on the MDC, please refer to
L<Log::Log4perl/"Mapped Diagnostic Context (MDC)"> and
L<Log::Log4perl::MDC>.
=head2 My application is already logging to a file. How can I duplicate all messages to also go to the screen?
Assuming that you already have a Log4perl configuration file like
log4perl.logger = DEBUG, FileApp
log4perl.appender.FileApp = Log::Log4perl::Appender::File
log4perl.appender.FileApp.filename = test.log
log4perl.appender.FileApp.layout = PatternLayout
log4perl.appender.FileApp.layout.ConversionPattern = %d> %m%n
and log statements all over your code,
it's very easy with Log4perl to have the same messages both printed to
the logfile and the screen. No reason to change your code, of course,
just add another appender to the configuration file and you're done:
log4perl.logger = DEBUG, FileApp, ScreenApp
log4perl.appender.FileApp = Log::Log4perl::Appender::File
log4perl.appender.FileApp.filename = test.log
log4perl.appender.FileApp.layout = PatternLayout
log4perl.appender.FileApp.layout.ConversionPattern = %d> %m%n
log4perl.appender.ScreenApp = Log::Log4perl::Appender::Screen
log4perl.appender.ScreenApp.stderr = 0
log4perl.appender.ScreenApp.layout = PatternLayout
log4perl.appender.ScreenApp.layout.ConversionPattern = %d> %m%n
The configuration file above is assuming that both appenders are
active in the same logger hierarchy, in this case the C<root> category.
But even if you've got file loggers defined in several parts of your system,
belonging to different logger categories,
each logging to different files, you can gobble up all logged messages
by defining a root logger with a screen appender, which would duplicate
messages from all your file loggers to the screen due to Log4perl's
appender inheritance. Check
http://www.perl.com/pub/a/2002/09/11/log4perl.html
for details. Have fun!
=head2 How can I make sure my application logs a message when it dies unexpectedly?
Whenever you encounter a fatal error in your application, instead of saying
something like
open FILE, "<blah" or die "Can't open blah -- bailing out!";
just use Log::Log4perl's fatal functions instead:
my $log = get_logger("Some::Package");
open FILE, "<blah" or $log->logdie("Can't open blah -- bailing out!");
This will both log the message with priority FATAL according to your current
Log::Log4perl configuration and then call Perl's C<die()>
afterwards to terminate the program. It works the same with
stealth loggers (see L<Log::Log4perl/"Stealth Loggers">),
all you need to do is call
use Log::Log4perl qw(:easy);
open FILE, "<blah" or LOGDIE "Can't open blah -- bailing out!";
What can you do if you're using some library which doesn't use Log::Log4perl
and calls C<die()> internally if something goes wrong? Use a
C<$SIG{__DIE__}> pseudo signal handler
use Log::Log4perl qw(get_logger);
$SIG{__DIE__} = sub {
if($^S) {
# We're in an eval {} and don't want log
# this message but catch it later
return;
}
local $Log::Log4perl::caller_depth =
$Log::Log4perl::caller_depth + 1;
my $logger = get_logger("");
$logger->fatal(@_);
die @_; # Now terminate really
};
This will catch every C<die()>-Exception of your
application or the modules it uses. In case you want to
It
will fetch a root logger and pass on the C<die()>-Message to it.
If you make sure you've configured with a root logger like this:
Log::Log4perl->init(\q{
log4perl.category = FATAL, Logfile
log4perl.appender.Logfile = Log::Log4perl::Appender::File
log4perl.appender.Logfile.filename = fatal_errors.log
log4perl.appender.Logfile.layout = \
Log::Log4perl::Layout::PatternLayout
log4perl.appender.Logfile.layout.ConversionPattern = %F{1}-%L (%M)> %m%n
});
then all C<die()> messages will be routed to a file properly. The line
local $Log::Log4perl::caller_depth =
$Log::Log4perl::caller_depth + 1;
in the pseudo signal handler above merits a more detailed explanation. With
the setup above, if a module calls C<die()> in one of its functions,
the fatal message will be logged in the signal handler and not in the
original function -- which will cause the %F, %L and %M placeholders
in the pattern layout to be replaced by the filename, the line number
and the function/method name of the signal handler, not the error-throwing
module. To adjust this, Log::Log4perl has the C<$caller_depth> variable,
which defaults to 0, but can be set to positive integer values
to offset the caller level. Increasing
it by one will cause it to log the calling function's parameters, not
the ones of the signal handler.
See L<Log::Log4perl/"Using Log::Log4perl from wrapper classes"> for more
details.
=head2 How can I hook up the LWP library with Log::Log4perl?
Or, to put it more generally: How can you utilize a third-party
library's embedded logging and debug statements in Log::Log4perl?
How can you make them print
to configurable appenders, turn them on and off, just as if they
were regular Log::Log4perl logging statements?
The easiest solution is to map the third-party library logging statements
to Log::Log4perl's stealth loggers via a typeglob assignment.
As an example, let's take LWP, one of the most popular Perl modules,
which makes handling WWW requests and responses a breeze.
Internally, LWP uses its own logging and debugging system,
utilizing the following calls
inside the LWP code (from the LWP::Debug man page):
# Function tracing
LWP::Debug::trace('send()');
# High-granular state in functions
LWP::Debug::debug('url ok');
# Data going over the wire
LWP::Debug::conns("read $n bytes: $data");
First, let's assign Log::Log4perl priorities
to these functions: I'd suggest that
C<debug()> messages have priority C<INFO>,
C<trace()> uses C<DEBUG> and C<conns()> also logs with C<DEBUG> --
although your mileage may certainly vary.
Now, in order to transpartently hook up LWP::Debug with Log::Log4perl,
all we have to do is say
package LWP::Debug;
use Log::Log4perl qw(:easy);
*trace = *INFO;
*conns = *DEBUG;
*debug = *DEBUG;
package main;
# ... go on with your regular program ...
at the beginning of our program. In this way, every time the, say,
C<LWP::UserAgent> module calls C<LWP::Debug::trace()>, it will implicitely
call INFO(), which is the C<info()> method of a stealth logger defined for
the Log::Log4perl category C<LWP::Debug>. Is this cool or what?
Here's a complete program:
use LWP::UserAgent;
use HTTP::Request::Common;
use Log::Log4perl qw(:easy);
Log::Log4perl->easy_init(
{ category => "LWP::Debug",
level => $DEBUG,
layout => "%r %p %M-%L %m%n",
});
package LWP::Debug;
use Log::Log4perl qw(:easy);
*trace = *INFO;
*conns = *DEBUG;
*debug = *DEBUG;
package main;
my $ua = LWP::UserAgent->new();
my $resp = $ua->request(GET "http://amazon.com");
if($resp->is_success()) {
print "Success: Received ",
length($resp->content()), "\n";
} else {
print "Error: ", $resp->code(), "\n";
}
This will generate the following output on STDERR:
174 INFO LWP::UserAgent::new-164 ()
208 INFO LWP::UserAgent::request-436 ()
211 INFO LWP::UserAgent::send_request-294 GET http://amazon.com
212 DEBUG LWP::UserAgent::_need_proxy-1123 Not proxied
405 INFO LWP::Protocol::http::request-122 ()
859 DEBUG LWP::Protocol::collect-206 read 233 bytes
863 DEBUG LWP::UserAgent::request-443 Simple response: Found
869 INFO LWP::UserAgent::request-436 ()
871 INFO LWP::UserAgent::send_request-294
GET http://www.amazon.com:80/exec/obidos/gateway_redirect
872 DEBUG LWP::UserAgent::_need_proxy-1123 Not proxied
873 INFO LWP::Protocol::http::request-122 ()
1016 DEBUG LWP::UserAgent::request-443 Simple response: Found
1020 INFO LWP::UserAgent::request-436 ()
1022 INFO LWP::UserAgent::send_request-294
GET http://www.amazon.com/exec/obidos/subst/home/home.html/
1023 DEBUG LWP::UserAgent::_need_proxy-1123 Not proxied
1024 INFO LWP::Protocol::http::request-122 ()
1382 DEBUG LWP::Protocol::collect-206 read 632 bytes
...
2605 DEBUG LWP::Protocol::collect-206 read 77 bytes
2607 DEBUG LWP::UserAgent::request-443 Simple response: OK
Success: Received 42584
Of course, in this way, the embedded logging and debug statements within
LWP can be utilized in any Log::Log4perl way you can think of. You can
have them sent to different appenders, block them based on the
category and everything else Log::Log4perl has to offer.
Only drawback of this method: Steering logging behavior via category
is always based on the C<LWP::Debug> package. Although the logging
statements reflect the package name of the issuing module properly,
the stealth loggers in C<LWP::Debug> are all of the category C<LWP::Debug>.
This implies that you can't control the logging behavior based on the
package that's I<initiating> a log request (e.g. LWP::UserAgent) but only
based on the package that's actually I<executing> the logging statement,
C<LWP::Debug> in this case.
To work around this conundrum, we need to write a wrapper function and
plant it into the C<LWP::Debug> package. It will determine the caller and
create a logger bound to a category with the same name as the caller's
package:
package LWP::Debug;
use Log::Log4perl qw(:levels get_logger);
sub l4p_wrapper {
my($prio, @message) = @_;
$Log::Log4perl::caller_depth += 2;
get_logger(scalar caller(1))->log($prio, @message);
$Log::Log4perl::caller_depth -= 2;
}
no warnings 'redefine';
*trace = sub { l4p_wrapper($INFO, @_); };
*debug = *conns = sub { l4p_wrapper($DEBUG, @_); };
package main;
# ... go on with your main program ...
This is less performant than the previous approach, because every
log request will request a reference to a logger first, then call
the wrapper, which will in turn call the appropriate log function.
This hierarchy shift has to be compensated for by increasing
C<$Log::Log4perl::caller_depth> by 2 before calling the log function
and decreasing it by 2 right afterwards. Also, the C<l4p_wrapper>
function shown above calls C<caller(1)> which determines the name
of the package I<two> levels down the calling hierarchy (and
therefore compensates for both the wrapper function and the
anonymous subroutine calling it).
C<no warnings 'redefine'> suppresses a warning Perl would generate
otherwise
upon redefining C<LWP::Debug>'s C<trace()>, C<debug()> and C<conns()>
functions. In case you use a perl prior to 5.6.x, you need
to manipulate C<$^W> instead.
To make things easy for you when dealing with LWP, Log::Log4perl 0.47
introduces C<Log::Log4perl-E<gt>infiltrate_lwp()> which does exactly the
above.
=head2 What if I need dynamic values in a static Log4perl configuration file?
Say, your application uses Log::Log4perl for logging and
therefore comes with a Log4perl configuration file, specifying the logging
behavior.
But, you also want it to take command line parameters to set values
like the name of the log file.
How can you have
both a static Log4perl configuration file and a dynamic command line
interface?
As of Log::Log4perl 0.28, every value in the configuration file
can be specified as a I<Perl hook>. So, instead of saying
log4perl.appender.Logfile.filename = test.log
you could just as well have a Perl subroutine deliver the value
dynamically:
log4perl.appender.Logfile.filename = sub { logfile(); };
given that C<logfile()> is a valid function in your C<main> package
returning a string containing the path to the log file.
Or, think about using the value of an environment variable:
log4perl.appender.DBI.user = sub { $ENV{USERNAME} };
When C<Log::Log4perl-E<gt>init()> parses the configuration
file, it will notice the assignment above because of its
C<sub {...}> pattern and treat it in a special way:
It will evaluate the subroutine (which can contain
arbitrary Perl code) and take its return value as the right side
of the assignment.
A typical application would be called like this on the command line:
app # log file is "test.log"
app -l mylog.txt # log file is "mylog.txt"
Here's some sample code implementing the command line interface above:
use Log::Log4perl qw(get_logger);
use Getopt::Std;
getopt('l:', \our %OPTS);
my $conf = q(
log4perl.category.Bar.Twix = WARN, Logfile
log4perl.appender.Logfile = Log::Log4perl::Appender::File
log4perl.appender.Logfile.filename = sub { logfile(); };
log4perl.appender.Logfile.layout = SimpleLayout
);
Log::Log4perl::init(\$conf);
my $logger = get_logger("Bar::Twix");
$logger->error("Blah");
###########################################
sub logfile {
###########################################
if(exists $OPTS{l}) {
return $OPTS{l};
} else {
return "test.log";
}
}
Every Perl hook may contain arbitrary perl code,
just make sure to fully qualify eventual variable names
(e.g. C<%main::OPTS> instead of C<%OPTS>).
B<SECURITY NOTE>: this feature means arbitrary perl code
can be embedded in the config file. In the rare case
where the people who have access to your config file
are different from the people who write your code and
shouldn't have execute rights, you might want to call
$Log::Log4perl::Config->allow_code(0);
before you call init(). This will prevent Log::Log4perl from
executing I<any> Perl code in the config file (including
code for custom conversion specifiers
(see L<Log::Log4perl::Layout::PatternLayout/"Custom cspecs">).
=head2 How can I roll over my logfiles automatically at midnight?
Long-running applications tend to produce ever-increasing logfiles.
For backup and cleanup purposes, however, it is often desirable to move
the current logfile to a different location from time to time and
start writing a new one.
This is a non-trivial task, because it has to happen in sync with
the logging system in order not to lose any messages in the process.
Luckily, I<Mark Pfeiffer>'s C<Log::Dispatch::FileRotate> appender
works well with Log::Log4perl to rotate your logfiles in a variety of ways.
Note, however, that having the application deal with rotating a log
file is not cheap. Among other things, it requires locking the log file
with every write to avoid race conditions.
There are good reasons to use external rotators like C<newsyslog>
instead.
See the entry C<How can I rotate a logfile with newsyslog?> in the
FAQ for more information on how to configure it.
When using C<Log::Dispatch::FileRotate>,
all you have to do is specify it in your Log::Log4perl configuration file
and your logfiles will be rotated automatically.
You can choose between rolling based on a maximum size ("roll if greater
than 10 MB") or based on a date pattern ("roll everyday at midnight").
In both cases, C<Log::Dispatch::FileRotate> allows you to define a
number C<max> of saved files to keep around until it starts overwriting
the oldest ones. If you set the C<max> parameter to 2 and the name of
your logfile is C<test.log>, C<Log::Dispatch::FileRotate> will
move C<test.log> to C<test.log.1> on the first rollover. On the second
rollover, it will move C<test.log.1> to C<test.log.2> and then C<test.log>
to C<test.log.1>. On the third rollover, it will move C<test.log.1> to
C<test.log.2> (therefore discarding the old C<test.log.2>) and
C<test.log> to C<test.log.1>. And so forth. This way, there's always
going to be a maximum of 2 saved log files around.
Here's an example of a Log::Log4perl configuration file, defining a
daily rollover at midnight (date pattern C<yyyy-MM-dd>), keeping
a maximum of 5 saved logfiles around:
log4perl.category = WARN, Logfile
log4perl.appender.Logfile = Log::Dispatch::FileRotate
log4perl.appender.Logfile.filename = test.log
log4perl.appender.Logfile.max = 5
log4perl.appender.Logfile.DatePattern = yyyy-MM-dd
log4perl.appender.Logfile.TZ = PST
log4perl.appender.Logfile.layout = \
Log::Log4perl::Layout::PatternLayout
log4perl.appender.Logfile.layout.ConversionPattern = %d %m %n
Please see the C<Log::Dispatch::FileRotate> documentation for details.
C<Log::Dispatch::FileRotate> is available on CPAN.
=head2 What's the easiest way to turn off all logging, even with a lengthy Log4perl configuration file?
In addition to category-based levels and appender thresholds,
Log::Log4perl supports system-wide logging thresholds. This is the
minimum level the system will require of any logging events in order for them
to make it through to any configured appenders.
For example, putting the line
log4perl.threshold = ERROR
anywhere in your configuration file will limit any output to any appender
to events with priority of ERROR or higher (ERROR or FATAL that is).
However, in order to suppress all logging entirely, you need to use a
priority that's higher than FATAL: It is simply called C<OFF>, and it is never
used by any logger. By definition, it is higher than the highest
defined logger level.
Therefore, if you keep the line
log4perl.threshold = OFF
somewhere in your Log::Log4perl configuration, the system will be quiet
as a graveyard. If you deactivate the line (e.g. by commenting it out),
the system will, upon config reload, snap back to normal operation, providing
logging messages according to the rest of the configuration file again.
=head2 I keep getting duplicate log messages! What's wrong?
Having several settings for related categories in the Log4perl
configuration file sometimes leads to a phenomenon called
"message duplication". It can be very confusing at first,
but if thought through properly, it turns out that Log4perl behaves
as advertised. But, don't despair, of course there's a number of
ways to avoid message duplication in your logs.
Here's a sample Log4perl configuration file that produces the
phenomenon:
log4perl.logger.Cat = ERROR, Screen
log4perl.logger.Cat.Subcat = WARN, Screen
log4perl.appender.Screen = Log::Log4perl::Appender::Screen
log4perl.appender.Screen.layout = SimpleLayout
It defines two loggers, one for category C<Cat> and one for
C<Cat::Subcat>, which is obviously a subcategory of C<Cat>.
The parent logger has a priority setting of ERROR, the child
is set to the lower C<WARN> level.
Now imagine the following code in your program:
my $logger = get_logger("Cat.Subcat");
$logger->warn("Warning!");
What do you think will happen? An unexperienced Log4perl user
might think: "Well, the message is being sent with level WARN, so the
C<Cat::Subcat> logger will accept it and forward it to the
attached C<Screen> appender. Then, the message will percolate up
the logger hierarchy, find
the C<Cat> logger, which will suppress the message because of its
ERROR setting."
But, perhaps surprisingly, what you'll get with the
code snippet above is not one but two log messages written
to the screen:
WARN - Warning!
WARN - Warning!
What happened? The culprit is that once the logger C<Cat::Subcat>
decides to fire, it will forward the message I<unconditionally>
to all directly or indirectly attached appenders. The C<Cat> logger
will never be asked if it wants the message or not -- the message
will just be pushed through to the appender attached to C<Cat>.
One way to prevent the message from bubbling up the logger
hierarchy is to set the C<additivity> flag of the subordinate logger to
C<0>:
log4perl.logger.Cat = ERROR, Screen
log4perl.logger.Cat.Subcat = WARN, Screen
log4perl.additivity.Cat.Subcat = 0
log4perl.appender.Screen = Log::Log4perl::Appender::Screen
log4perl.appender.Screen.layout = SimpleLayout
The message will now be accepted by the C<Cat::Subcat> logger,
forwarded to its appender, but then C<Cat::Subcat> will suppress
any further action. While this setting avoids duplicate messages
as seen before, it is often not the desired behavior. Messages
percolating up the hierarchy are a useful Log4perl feature.
If you're defining I<different> appenders for the two loggers,
one other option is to define an appender threshold for the
higher-level appender. Typically it is set to be
equal to the logger's level setting:
log4perl.logger.Cat = ERROR, Screen1
log4perl.logger.Cat.Subcat = WARN, Screen2
log4perl.appender.Screen1 = Log::Log4perl::Appender::Screen
log4perl.appender.Screen1.layout = SimpleLayout
log4perl.appender.Screen1.Threshold = ERROR
log4perl.appender.Screen2 = Log::Log4perl::Appender::Screen
log4perl.appender.Screen2.layout = SimpleLayout
Since the C<Screen1> appender now blocks every message with
a priority less than ERROR, even if the logger in charge
lets it through, the message percolating up the hierarchy is
being blocked at the last minute and I<not> appended to C<Screen1>.
So far, we've been operating well within the boundaries of the
Log4j standard, which Log4perl adheres to. However, if
you would really, really like to use a single appender
and keep the message percolation intact without having to deal
with message duplication, there's a non-standard solution for you:
log4perl.logger.Cat = ERROR, Screen
log4perl.logger.Cat.Subcat = WARN, Screen
log4perl.appender.Screen = Log::Log4perl::Appender::Screen
log4perl.appender.Screen.layout = SimpleLayout
log4perl.oneMessagePerAppender = 1
The C<oneMessagePerAppender> flag will suppress duplicate messages
to the same appender. Again, that's non-standard. But way cool :).
=head2 How can I configure Log::Log4perl to send me email if something happens?
Some incidents require immediate action. You can't wait until someone
checks the log files, you need to get notified on your pager right away.
The easiest way to do that is by using the C<Log::Dispatch::Email::MailSend>
module as an appender. It comes with the C<Log::Dispatch> bundle and
allows you to specify recipient and subject of outgoing emails in the Log4perl
configuration file:
log4perl.category = FATAL, Mailer
log4perl.appender.Mailer = Log::Dispatch::Email::MailSend
log4perl.appender.Mailer.to = drone@pageme.net
log4perl.appender.Mailer.subject = Something's broken!
log4perl.appender.Mailer.layout = SimpleLayout
The message of every log incident this appender gets
will then be forwarded to the given
email address. Check the C<Log::Dispatch::Email::MailSend> documentation
for details. And please make sure there's not a flood of email messages
sent out by your application, filling up the receipient's inbox.
There's one caveat you need to know about: The C<Log::Dispatch::Email>
hierarchy of appenders turns on I<buffering> by default. This means that
the appender will not send out messages right away but wait until a
certain threshold has been reached. If you'd rather have your alerts
sent out immeditately, use
log4perl.appender.Mailer.buffered = 0
to turn buffering off.
=head2 How can I write my own appender?
First off, Log::Log4perl comes with a set of standard appenders. Then,
there's a lot of Log4perl-compatible appenders already
available on CPAN: Just run a search for C<Log::Dispatch> on
http://search.cpan.org and chances are that what you're looking for
has already been developed, debugged and been used successfully
in production -- no need for you to reinvent the wheel.
Also, Log::Log4perl ships with a nifty database appender named
Log::Log4perl::Appender::DBI -- check it out if talking to databases is your
desire.
But if you're up for a truly exotic task, you might have to write
an appender yourself. That's very easy -- it takes no longer
than a couple of minutes.
Say, we wanted to create an appender of the class
C<ColorScreenAppender>, which logs messages
to the screen in a configurable color. Just create a new class
in C<ColorScreenAppender.pm>:
package ColorScreenAppender;
Now let's assume that your Log::Log4perl
configuration file C<test.conf> looks like this:
log4perl.logger = INFO, ColorApp
log4perl.appender.ColorApp=ColorScreenAppender
log4perl.appender.ColorApp.color=blue
log4perl.appender.ColorApp.layout = PatternLayout
log4perl.appender.ColorApp.layout.ConversionPattern=%d %m %n
This will cause Log::Log4perl on C<init()> to look for a class
ColorScreenAppender and call its constructor new(). Let's add
new() to ColorScreenAppender.pm:
sub new {
my($class, %options) = @_;
my $self = { %options };
bless $self, $class;
return $self;
}
To initialize this appender, Log::Log4perl will call
and pass all attributes of the appender as defined in the configuration
file to the constructor as name/value pairs (in this case just one):
ColorScreenAppender->new(color => "blue");
The new() method listed above stores the contents of the
%options hash in the object's
instance data hash (referred to by $self).
That's all for initializing a new appender with Log::Log4perl.
Second, ColorScreenAppender needs to expose a
C<log()> method, which will be called by Log::Log4perl
every time it thinks the appender should fire. Along with the
object reference (as usual in Perl's object world), log()
will receive a list of name/value pairs, of which only the one
under the key C<message> shall be of interest for now since it is the
message string to be logged. At this point, Log::Log4perl has already taken
care of joining the message to be a single string.
For our special appender ColorScreenAppender, we're using the
Term::ANSIColor module to colorize the output:
use Term::ANSIColor;
sub log {
my($self, %params) = @_;
print colored($params{message},
$self->{color});
}
The color (as configured in the Log::Log4perl configuration file)
is available as $self-E<gt>{color} in the appender object. Don't
forget to return
1;
at the end of ColorScreenAppender.pm and you're done. Install the new appender
somewhere where perl can find it and try it with a test script like
use Log::Log4perl qw(:easy);
Log::Log4perl->init("test.conf");
ERROR("blah");
to see the new colored output. Is this cool or what?
And it gets even better: You can write dynamically generated appender
classes using the C<Class::Prototyped> module. Here's an example of
an appender prepending every outgoing message with a configurable
number of bullets:
use Class::Prototyped;
my $class = Class::Prototyped->newPackage(
"MyAppenders::Bulletizer",
bullets => 1,
log => sub {
my($self, %params) = @_;
print "*" x $self->bullets(),
$params{message};
},
);
use Log::Log4perl qw(:easy);
Log::Log4perl->init(\ q{
log4perl.logger = INFO, Bully
log4perl.appender.Bully=MyAppenders::Bulletizer
log4perl.appender.Bully.bullets=3
log4perl.appender.Bully.layout = PatternLayout
log4perl.appender.Bully.layout.ConversionPattern=%m %n
});
# ... prints: "***Boo!\n";
INFO "Boo!";
=head2 How can I drill down on references before logging them?
If you've got a reference to a nested structure or object, then
you probably don't want to log it as C<HASH(0x81141d4)> but rather
dump it as something like
$VAR1 = {
'a' => 'b',
'd' => 'e'
};
via a module like Data::Dumper. While it's syntactically correct to say
$logger->debug(Data::Dumper::Dumper($ref));
this call imposes a huge performance penalty on your application
if the message is suppressed by Log::Log4perl, because Data::Dumper
will perform its expensive operations in any case, because it doesn't
know that its output will be thrown away immediately.
As of Log::Log4perl 0.28, there's a better way: Use the
message output filter format as in
$logger->debug( {filter => \&Data::Dumper::Dumper,
value => $ref} );
and Log::Log4perl won't call the filter function unless the message really
gets written out to an appender. Just make sure to pass the whole slew as a
reference to a hash specifying a filter function (as a sub reference)
under the key C<filter> and the value to be passed to the filter function in
C<value>).
When it comes to logging, Log::Log4perl will call the filter function,
pass the C<value> as an argument and log the return value.
Saves you serious cycles.
=head2 How can I collect all FATAL messages in an extra log file?
Suppose you have employed Log4perl all over your system and you've already
activated logging in various subsystems. On top of that, without disrupting
any other settings, how can you collect all FATAL messages all over the system
and send them to a separate log file?
If you define a root logger like this:
log4perl.logger = FATAL, File
log4perl.appender.File = Log::Log4perl::Appender::File
log4perl.appender.File.filename = /tmp/fatal.txt
log4perl.appender.File.layout = PatternLayout
log4perl.appender.File.layout.ConversionPattern= %d %m %n
# !!! Something's missing ...
you'll be surprised to not only receive all FATAL messages
issued anywhere in the system,
but also everything else -- gazillions of
ERROR, WARN, INFO and even DEBUG messages will end up in
your fatal.txt logfile!
Reason for this is Log4perl's (or better: Log4j's) appender additivity.
Once a
lower-level logger decides to fire, the message is going to be forwarded
to all appenders upstream -- without further priority checks with their
attached loggers.
There's a way to prevent this, however: If your appender defines a
minimum threshold, only messages of this priority or higher are going
to be logged. So, just add
log4perl.appender.File.Threshold = FATAL
to the configuration above, and you'll get what you wanted in the
first place: An overall system FATAL message collector.
=head2 How can I bundle several log messages into one?
Would you like to tally the messages arriving at your appender and
dump out a summary once they're exceeding a certain threshold?
So that something like
$logger->error("Blah");
$logger->error("Blah");
$logger->error("Blah");
won't be logged as
Blah
Blah
Blah
but as
[3] Blah
instead? If you'd like to hold off on logging a message until it has been
sent a couple of times, you can roll that out by creating a buffered
appender.
Let's define a new appender like
package TallyAppender;
sub new {
my($class, %options) = @_;
my $self = { maxcount => 5,
%options
};
bless $self, $class;
$self->{last_message} = "";
$self->{last_message_count} = 0;
return $self;
}
with two additional instance variables C<last_message> and
C<last_message_count>, storing the content of the last message sent
and a counter of how many times this has happened. Also, it features
a configuration parameter C<maxcount> which defaults to 5 in the
snippet above but can be set in the Log4perl configuration file like this:
log4perl.logger = INFO, A
log4perl.appender.A=TallyAppender
log4perl.appender.A.maxcount = 3
The main tallying logic lies in the appender's C<log> method,
which is called every time Log4perl thinks a message needs to get logged
by our appender:
sub log {
my($self, %params) = @_;
# Message changed? Print buffer.
if($self->{last_message} and
$params{message} ne $self->{last_message}) {
print "[$self->{last_message_count}]: " .
"$self->{last_message}";
$self->{last_message_count} = 1;
$self->{last_message} = $params{message};
return;
}
$self->{last_message_count}++;
$self->{last_message} = $params{message};
# Threshold exceeded? Print, reset counter
if($self->{last_message_count} >=
$self->{maxcount}) {
print "[$self->{last_message_count}]: " .
"$params{message}";
$self->{last_message_count} = 0;
$self->{last_message} = "";
return;
}
}
We basically just check if the oncoming message in C<$param{message}>
is equal to what we've saved before in the C<last_message> instance
variable. If so, we're increasing C<last_message_count>.
We print the message in two cases: If the new message is different
than the buffered one, because then we need to dump the old stuff
and store the new. Or, if the counter exceeds the threshold, as
defined by the C<maxcount> configuration parameter.
Please note that the appender always gets the fully rendered message and
just compares it as a whole -- so if there's a date/timestamp in there,
that might confuse your logic. You can work around this by specifying
%m %n as a layout and add the date later on in the appender. Or, make
the comparison smart enough to omit the date.
At last, don't forget what happens if the program is being shut down.
If there's still messages in the buffer, they should be printed out
at that point. That's easy to do in the appender's DESTROY method,
which gets called at object destruction time:
sub DESTROY {
my($self) = @_;
if($self->{last_message_count}) {
print "[$self->{last_message_count}]: " .
"$self->{last_message}";
return;
}
}
This will ensure that none of the buffered messages are lost.
Happy buffering!
=head2 I want to log ERROR and WARN messages to different files! How can I do that?
Let's assume you wanted to have each logging statement written to a
different file, based on the statement's priority. Messages with priority
C<WARN> are supposed to go to C</tmp/app.warn>, events prioritized
as C<ERROR> should end up in C</tmp/app.error>.
Now, if you define two appenders C<AppWarn> and C<AppError>
and assign them both to the root logger,
messages bubbling up from any loggers below will be logged by both
appenders because of Log4perl's message propagation feature. If you limit
their exposure via the appender threshold mechanism and set
C<AppWarn>'s threshold to C<WARN> and C<AppError>'s to C<ERROR>, you'll
still get C<ERROR> messages in C<AppWarn>, because C<AppWarn>'s C<WARN>
setting will just filter out messages with a I<lower> priority than
C<WARN> -- C<ERROR> is higher and will be allowed to pass through.
What we need for this is a Log4perl I<Custom Filter>, available with
Log::Log4perl 0.30.
Both appenders need to verify that
the priority of the oncoming messages exactly I<matches> the priority
the appender is supposed to log messages of. To accomplish this task,
let's define two custom filters, C<MatchError> and C<MatchWarn>, which,
when attached to their appenders, will limit messages passed on to them
to those matching a given priority:
log4perl.logger = WARN, AppWarn, AppError
# Filter to match level ERROR
log4perl.filter.MatchError = Log::Log4perl::Filter::LevelMatch
log4perl.filter.MatchError.LevelToMatch = ERROR
log4perl.filter.MatchError.AcceptOnMatch = true
# Filter to match level WARN
log4perl.filter.MatchWarn = Log::Log4perl::Filter::LevelMatch
log4perl.filter.MatchWarn.LevelToMatch = WARN
log4perl.filter.MatchWarn.AcceptOnMatch = true
# Error appender
log4perl.appender.AppError = Log::Log4perl::Appender::File
log4perl.appender.AppError.filename = /tmp/app.err
log4perl.appender.AppError.layout = SimpleLayout
log4perl.appender.AppError.Filter = MatchError
# Warning appender
log4perl.appender.AppWarn = Log::Log4perl::Appender::File
log4perl.appender.AppWarn.filename = /tmp/app.warn
log4perl.appender.AppWarn.layout = SimpleLayout
log4perl.appender.AppWarn.Filter = MatchWarn
The appenders C<AppWarn> and C<AppError> defined above are logging to C</tmp/app.warn> and
C</tmp/app.err> respectively and have the custom filters C<MatchWarn> and C<MatchError>
attached.
This setup will direct all WARN messages, issued anywhere in the system, to /tmp/app.warn (and
ERROR messages to /tmp/app.error) -- without any overlaps.
=head2 On our server farm, Log::Log4perl configuration files differ slightly from host to host. Can I roll them all into one?
You sure can, because Log::Log4perl allows you to specify attribute values
dynamically. Let's say that one of your appenders expects the host's IP address
as one of its attributes. Now, you could certainly roll out different
configuration files for every host and specify the value like
log4perl.appender.MyAppender = Log::Log4perl::Appender::SomeAppender
log4perl.appender.MyAppender.ip = 10.0.0.127
but that's a maintenance nightmare. Instead, you can have Log::Log4perl
figure out the IP address at configuration time and set the appender's
value correctly:
# Set the IP address dynamically
log4perl.appender.MyAppender = Log::Log4perl::Appender::SomeAppender
log4perl.appender.MyAppender.ip = sub { \
use Sys::Hostname; \
use Socket; \
return inet_ntoa(scalar gethostbyname hostname); \
}
If Log::Log4perl detects that an attribute value starts with something like
C<"sub {...">, it will interpret it as a perl subroutine which is to be executed
once at configuration time (not runtime!) and its return value is
to be used as the attribute value. This comes in handy
for rolling out applications whichs Log::Log4perl configuration files
show small host-specific differences, because you can deploy the unmodified
application distribution on all instances of the server farm.
=head2 Log4perl doesn't interpret my backslashes correctly!
If you're using Log4perl's feature to specify the configuration as a
string in your program (as opposed to a separate configuration file),
chances are that you've written it like this:
# *** WRONG! ***
Log::Log4perl->init( \ <<END_HERE);
log4perl.logger = WARN, A1
log4perl.appender.A1 = Log::Log4perl::Appender::Screen
log4perl.appender.A1.layout = \
Log::Log4perl::Layout::PatternLayout
log4perl.appender.A1.layout.ConversionPattern = %m%n
END_HERE
# *** WRONG! ***
and you're getting the following error message:
Layout not specified for appender A1 at .../Config.pm line 342.
What's wrong? The problem is that you're using a here-document with
substitution enabled (C<E<lt>E<lt>END_HERE>) and that Perl won't
interpret backslashes at line-ends as continuation characters but
will essentially throw them out. So, in the code above, the layout line
will look like
log4perl.appender.A1.layout =
to Log::Log4perl which causes it to report an error. To interpret the backslash
at the end of the line correctly as a line-continuation character, use
the non-interpreting mode of the here-document like in
# *** RIGHT! ***
Log::Log4perl->init( \ <<'END_HERE');
log4perl.logger = WARN, A1
log4perl.appender.A1 = Log::Log4perl::Appender::Screen
log4perl.appender.A1.layout = \
Log::Log4perl::Layout::PatternLayout
log4perl.appender.A1.layout.ConversionPattern = %m%n
END_HERE
# *** RIGHT! ***
(note the single quotes around C<'END_HERE'>) or use C<q{...}>
instead of a here-document and Perl will treat the backslashes at
line-end as intended.
=head2 I want to suppress certain messages based on their content!
Let's assume you've plastered all your functions with Log4perl
statements like
sub some_func {
INFO("Begin of function");
# ... Stuff happens here ...
INFO("End of function");
}
to issue two log messages, one at the beginning and one at the end of
each function. Now you want to suppress the message at the beginning
and only keep the one at the end, what can you do? You can't use the category
mechanism, because both messages are issued from the same package.
Log::Log4perl's custom filters (0.30 or better) provide an interface for the
Log4perl user to step in right before a message gets logged and decide if
it should be written out or suppressed, based on the message content or other
parameters:
use Log::Log4perl qw(:easy);
Log::Log4perl::init( \ <<'EOT' );
log4perl.logger = INFO, A1
log4perl.appender.A1 = Log::Log4perl::Appender::Screen
log4perl.appender.A1.layout = \
Log::Log4perl::Layout::PatternLayout
log4perl.appender.A1.layout.ConversionPattern = %m%n
log4perl.filter.M1 = Log::Log4perl::Filter::StringMatch
log4perl.filter.M1.StringToMatch = Begin
log4perl.filter.M1.AcceptOnMatch = false
log4perl.appender.A1.Filter = M1
EOT
The last four statements in the configuration above are defining a custom
filter C<M1> of type C<Log::Log4perl::Filter::StringMatch>, which comes with
Log4perl right out of the box and allows you to define a text pattern to match
(as a perl regular expression) and a flag C<AcceptOnMatch> indicating
if a match is supposed to suppress the message or let it pass through.
The last line then assigns this filter to the C<A1> appender, which will
call it every time it receives a message to be logged and throw all
messages out I<not> matching the regular expression C<Begin>.
Instead of using the standard C<Log::Log4perl::Filter::StringMatch> filter,
you can define your own, simply using a perl subroutine:
log4perl.filter.ExcludeBegin = sub { !/Begin/ }
log4perl.appender.A1.Filter = ExcludeBegin
For details on custom filters, check L<Log::Log4perl::Filter>.
=head2 My new module uses Log4perl -- but what happens if the calling program didn't configure it?
If a Perl module uses Log::Log4perl, it will typically rely on the
calling program to initialize it. If it is using Log::Log4perl in C<:easy>
mode, like in
package MyMod;
use Log::Log4perl qw(:easy);
sub foo {
DEBUG("In foo");
}
1;
and the calling program doesn't initialize Log::Log4perl at all (e.g. because
it has no clue that it's available), Log::Log4perl will silently
ignore all logging messages. However, if the module is using Log::Log4perl
in regular mode like in
package MyMod;
use Log::Log4perl qw(get_logger);
sub foo {
my $logger = get_logger("");
$logger->debug("blah");
}
1;
and the main program is just using the module like in
use MyMode;
MyMode::foo();
then Log::Log4perl will also ignore all logging messages but
issue a warning like
Log4perl: Seems like no initialization happened.
Forgot to call init()?
(only once!) to remind novice users to not forget to initialize
the logging system before using it.
However, if you want to suppress this message, just
add the C<:nowarn> target to the module's C<use Log::Log4perl> call:
use Log::Log4perl qw(get_logger :nowarn);
This will have Log::Log4perl silently ignore all logging statements if
no initialization has taken place. If, instead of using init(), you're
using Log4perl's API to define loggers and appenders, the same
notification happens if no call to add_appenders() is made, i.e. no
appenders are defined.
If the module wants to figure out if some other program part has
already initialized Log::Log4perl, it can do so by calling
Log::Log4perl::initialized()
which will return a true value in case Log::Log4perl has been initialized
and a false value if not.
=head2 How can I synchronize access to an appender?
If you're using the same instance of an appender in multiple processes,
and each process is passing on messages to the appender in parallel,
you might end up with overlapping log entries.
Typical scenarios include a file appender that you create in the main
program, and which will then be shared between the parent and a
forked child process. Or two separate processes, each initializing a
Log4perl file appender on the same logfile.
Log::Log4perl won't synchronize access to the shared logfile by
default. Depending on your operating system's flush mechanism,
buffer size and the size of your messages, there's a small chance of
an overlap.
The easiest way to prevent overlapping messages in logfiles written to
by multiple processes is setting the
file appender's C<syswrite> flag along with a file write mode of C<"append">.
This makes sure that
C<Log::Log4perl::Appender::File> uses C<syswrite()> (which is guaranteed
to run uninterrupted) instead of C<print()> which might buffer
the message or get interrupted by the OS while it is writing. And in
C<"append"> mode, the OS kernel ensures that multiple processes share
one end-of-file marker, ensuring that each process writes to the I<real>
end of the file. (The value of C<"append">
for the C<mode> parameter is the default setting in Log4perl's file
appender so you don't have to set it explicitly.)
# Guarantees atomic writes
log4perl.category.Bar.Twix = WARN, Logfile
log4perl.appender.Logfile = Log::Log4perl::Appender::File
log4perl.appender.Logfile.mode = append
log4perl.appender.Logfile.syswrite = 1
log4perl.appender.Logfile.filename = test.log
log4perl.appender.Logfile.layout = SimpleLayout
Another guaranteed way of having messages separated with any kind of
appender is putting a Log::Log4perl::Appender::Synchronized composite
appender in between Log::Log4perl and the real appender. It will make
sure to let messages pass through this virtual gate one by one only.
Here's a sample configuration to synchronize access to a file appender:
log4perl.category.Bar.Twix = WARN, Syncer
log4perl.appender.Logfile = Log::Log4perl::Appender::File
log4perl.appender.Logfile.autoflush = 1
log4perl.appender.Logfile.filename = test.log
log4perl.appender.Logfile.layout = SimpleLayout
log4perl.appender.Syncer = Log::Log4perl::Appender::Synchronized
log4perl.appender.Syncer.appender = Logfile
C<Log::Log4perl::Appender::Synchronized> uses
the C<IPC::Shareable> module and its semaphores, which will slow down writing
the log messages, but ensures sequential access featuring atomic checks.
Check L<Log::Log4perl::Appender::Synchronized> for details.
=head2 Can I use Log::Log4perl with log4j's Chainsaw?
Yes, Log::Log4perl can be configured to send its events to log4j's
graphical log UI I<Chainsaw>.
=for html
<p>
<TABLE><TR><TD>
<A HREF="http://log4perl.sourceforge.net/images/chainsaw2.jpg"><IMG SRC="http://log4perl.sourceforge.net/images/chainsaw2s.jpg"></A>
<TR><TD>
<I>Figure 1: Chainsaw receives Log::Log4perl events</I>
</TABLE>
<p>
=for text
Figure1: Chainsaw receives Log::Log4perl events
Here's how it works:
=over 4
=item *
Get Guido Carls' E<lt>gcarls@cpan.orgE<gt> Log::Log4perl extension
C<Log::Log4perl::Layout::XMLLayout> from CPAN and install it:
perl -MCPAN -eshell
cpan> install Log::Log4perl::Layout::XMLLayout
=item *
Install and start Chainsaw, which is part of the C<log4j> distribution now
(see http://jakarta.apache.org/log4j ). Create a configuration file like
<log4j:configuration debug="true">
<plugin name="XMLSocketReceiver"
class="org.apache.log4j.net.XMLSocketReceiver">
<param name="decoder" value="org.apache.log4j.xml.XMLDecoder"/>
<param name="Port" value="4445"/>
</plugin>
<root> <level value="debug"/> </root>
</log4j:configuration>
and name it e.g. C<config.xml>. Then start Chainsaw like
java -Dlog4j.debug=true -Dlog4j.configuration=config.xml \
-classpath ".:log4j-1.3alpha.jar:log4j-chainsaw-1.3alpha.jar" \
org.apache.log4j.chainsaw.LogUI
and watch the GUI coming up.
=item *
Configure Log::Log4perl to use a socket appender with an XMLLayout, pointing
to the host/port where Chainsaw (as configured above) is waiting with its
XMLSocketReceiver:
use Log::Log4perl qw(get_logger);
use Log::Log4perl::Layout::XMLLayout;
my $conf = q(
log4perl.category.Bar.Twix = WARN, Appender
log4perl.appender.Appender = Log::Log4perl::Appender::Socket
log4perl.appender.Appender.PeerAddr = localhost
log4perl.appender.Appender.PeerPort = 4445
log4perl.appender.Appender.layout = Log::Log4perl::Layout::XMLLayout
);
Log::Log4perl::init(\$conf);
# Nasty hack to suppress encoding header
my $app = Log::Log4perl::appenders->{"Appender"};
$app->layout()->{enc_set} = 1;
my $logger = get_logger("Bar.Twix");
$logger->error("One");
The nasty hack shown in the code snippet above is currently (October 2003)
necessary, because Chainsaw expects XML messages to arrive in a format like
<log4j:event logger="Bar.Twix"
timestamp="1066794904310"
level="ERROR"
thread="10567">
<log4j:message><![CDATA[Two]]></log4j:message>
<log4j:NDC><![CDATA[undef]]></log4j:NDC>
<log4j:locationInfo class="main"
method="main"
file="./t"
line="32">
</log4j:locationInfo>
</log4j:event>
without a preceding
<?xml version = "1.0" encoding = "iso8859-1"?>
which Log::Log4perl::Layout::XMLLayout applies to the first event sent
over the socket.
=back
See figure 1 for a screenshot of Chainsaw in action, receiving events from
the Perl script shown above.
Many thanks to Chainsaw's
Scott Deboy <sdeboy@comotivsystems.com> for his support!
=head2 How can I run Log::Log4perl under mod_perl?
In persistent environments it's important to play by the rules outlined
in section L<Log::Log4perl/"Initialize once and only once">.
If you haven't read this yet, please go ahead and read it right now. It's
very important.
And no matter if you use a startup handler to init() Log::Log4perl or use the
init_once() strategy (added in 0.42), either way you're very likely to have
unsynchronized writes to logfiles.
If Log::Log4perl is configured with a log file appender, and it is
initialized via
the Apache startup handler, the file handle created initially will be
shared among all Apache processes. Similarly, with the init_once()
approach: although every process has a separate L4p configuration,
processes are gonna share the appender file I<names> instead, effectively
opening several different file handles on the same file.
Now, having several appenders using the same file handle or having
several appenders logging to the same file unsynchronized, this might
result in overlapping messages. Sometimes, this is acceptable. If it's
not, here's two strategies:
=over 4
=item *
Use the L<Log::Log4perl::Appender::Synchronized> appender to connect to
your file appenders. Here's the writeup:
http://log4perl.sourceforge.net/releases/Log-Log4perl/docs/html/Log/Log4perl/FAQ.html#23804
=item *
Use a different logfile for every process like in
#log4perl.conf
...
log4perl.appender.A1.filename = sub { "mylog.$$.log" }
=back
=head2 My program already uses warn() and die(). How can I switch to Log4perl?
If your program already uses Perl's C<warn()> function to spew out
error messages and you'd like to channel those into the Log4perl world,
just define a C<__WARN__> handler where your program or module resides:
use Log::Log4perl qw(:easy);
$SIG{__WARN__} = sub {
local $Log::Log4perl::caller_depth =
$Log::Log4perl::caller_depth + 1;
WARN @_;
};
Why the C<local> setting of C<$Log::Log4perl::caller_depth>?
If you leave that out,
C<PatternLayout> conversion specifiers like C<%M> or C<%F> (printing
the current function/method and source filename) will refer
to where the __WARN__ handler resides, not the environment
Perl's C<warn()> function was issued from. Increasing C<caller_depth>
adjusts for this offset. Having it C<local>, makes sure the level
gets set back after the handler exits.
Once done, if your program does something like
sub some_func {
warn "Here's a warning";
}
you'll get (depending on your Log::Log4perl configuration) something like
2004/02/19 20:41:02-main::some_func: Here's a warning at ./t line 25.
in the appropriate appender instead of having a screen full of STDERR
messages. It also works with the C<Carp> module and its C<carp()>
and C<cluck()> functions.
If, on the other hand, catching C<die()> and friends is
required, a C<__DIE__> handler is appropriate:
$SIG{__DIE__} = sub {
if($^S) {
# We're in an eval {} and don't want log
# this message but catch it later
return;
}
local $Log::Log4perl::caller_depth =
$Log::Log4perl::caller_depth + 1;
LOGDIE @_;
};
This will call Log4perl's C<LOGDIE()> function, which will log a fatal
error and then call die() internally, causing the program to exit. Works
equally well with C<Carp>'s C<croak()> and C<confess()> functions.
=head2 Some module prints messages to STDERR. How can I funnel them to Log::Log4perl?
If a module you're using doesn't use Log::Log4perl but prints logging
messages to STDERR instead, like
########################################
package IgnorantModule;
########################################
sub some_method {
print STDERR "Parbleu! An error!\n";
}
1;
there's still a way to capture these messages and funnel them
into Log::Log4perl, even without touching the module. What you need is
a trapper module like
########################################
package Trapper;
########################################
use Log::Log4perl qw(:easy);
sub TIEHANDLE {
my $class = shift;
bless [], $class;
}
sub PRINT {
my $self = shift;
$Log::Log4perl::caller_depth++;
DEBUG @_;
$Log::Log4perl::caller_depth--;
}
1;
and a C<tie> command in the main program to tie STDERR to the trapper
module along with regular Log::Log4perl initialization:
########################################
package main;
########################################
use Log::Log4perl qw(:easy);
Log::Log4perl->easy_init(
{level => $DEBUG,
file => 'stdout', # make sure not to use stderr here!
layout => "%d %M: %m%n",
});
tie *STDERR, "Trapper";
Make sure not to use STDERR as Log::Log4perl's file appender
here (which would be the default in C<:easy> mode), because it would
end up in an endless recursion.
Now, calling
IgnorantModule::some_method();
will result in the desired output
2004/05/06 11:13:04 IgnorantModule::some_method: Parbleu! An error!
=head2 How come PAR (Perl Archive Toolkit) creates executables which then can't find their Log::Log4perl appenders?
If not instructed otherwise, C<Log::Log4perl> dynamically pulls in
appender classes found in its configuration. If you specify
#!/usr/bin/perl
# mytest.pl
use Log::Log4perl qw(get_logger);
my $conf = q(
log4perl.category.Bar.Twix = WARN, Logfile
log4perl.appender.Logfile = Log::Log4perl::Appender::Screen
log4perl.appender.Logfile.layout = SimpleLayout
);
Log::Log4perl::init(\$conf);
my $logger = get_logger("Bar::Twix");
$logger->error("Blah");
then C<Log::Log4perl::Appender::Screen> will be pulled in while the program
runs, not at compile time. If you have PAR compile the script above to an
executable binary via
pp -o mytest mytest.pl
and then run C<mytest> on a machine without having Log::Log4perl installed,
you'll get an error message like
ERROR: can't load appenderclass 'Log::Log4perl::Appender::Screen'
Can't locate Log/Log4perl/Appender/Screen.pm in @INC ...
Why? At compile time, C<pp> didn't realize that
C<Log::Log4perl::Appender::Screen> would be needed later on and didn't
wrap it into the executable created. To avoid this, either say
C<use Log::Log4perl::Appender::Screen> in the script explicitly or
compile it with
pp -o mytest -M Log::Log4perl::Appender::Screen mytest.pl
to make sure the appender class gets included.
=head2 How can I access a custom appender defined in the configuration?
Any appender defined in the configuration file or somewhere in the code
can be accessed later via
C<Log::Log4perl-E<gt>appender_by_name("appender_name")>,
which returns a reference the the appender object.
Once you've got a hold of the object, it can be queried or modified to
your liking. For example, see the custom C<IndentAppender> defined below:
After calling C<init()> to define the Log4perl settings, the
appender object is retrieved to call its C<indent_more()> and C<indent_less()>
methods to control indentation of messages:
package IndentAppender;
sub new {
bless { indent => 0 }, $_[0];
}
sub indent_more { $_[0]->{indent}++ }
sub indent_less { $_[0]->{indent}-- }
sub log {
my($self, %params) = @_;
print " " x $self->{indent}, $params{message};
}
package main;
use Log::Log4perl qw(:easy);
my $conf = q(
log4perl.category = DEBUG, Indented
log4perl.appender.Indented = IndentAppender
log4perl.appender.Indented.layout = Log::Log4perl::Layout::SimpleLayout
);
Log::Log4perl::init(\$conf);
my $appender = Log::Log4perl->appender_by_name("Indented");
DEBUG "No identation";
$appender->indent_more();
DEBUG "One more";
$appender->indent_more();
DEBUG "Two more";
$appender->indent_less();
DEBUG "One less";
As you would expect, this will print
DEBUG - No identation
DEBUG - One more
DEBUG - Two more
DEBUG - One less
because the very appender used by Log4perl is modified dynamically at
runtime.
=head2 I don't know if Log::Log4perl is installed. How can I prepare my script?
In case your script needs to be prepared for environments that may or may
not have Log::Log4perl installed, there's a trick.
If you put the following BEGIN blocks at the top of the program,
you'll be able to use the DEBUG(), INFO(), etc. macros in
Log::Log4perl's C<:easy> mode.
If Log::Log4perl
is installed in the target environment, the regular Log::Log4perl rules
apply. If not, all of DEBUG(), INFO(), etc. are "stubbed" out, i.e. they
turn into no-ops:
use warnings;
use strict;
BEGIN {
eval { require Log::Log4perl; };
if($@) {
print "Log::Log4perl not installed - stubbing.\n";
no strict qw(refs);
*{"main::$_"} = sub { } for qw(DEBUG INFO WARN ERROR FATAL);
} else {
no warnings;
print "Log::Log4perl installed - life is good.\n";
require Log::Log4perl::Level;
Log::Log4perl::Level->import(__PACKAGE__);
Log::Log4perl->import(qw(:easy));
Log::Log4perl->easy_init($main::DEBUG);
}
}
# The regular script begins ...
DEBUG "Hey now!";
This snippet will first probe for Log::Log4perl, and if it can't be found,
it will alias DEBUG(), INFO(), with empty subroutines via typeglobs.
If Log::Log4perl is available, its level constants are first imported
(C<$DEBUG>, C<$INFO>, etc.) and then C<easy_init()> gets called to initialize
the logging system.
=head2 Can file appenders create files with different permissions?
Typically, when C<Log::Log4perl::Appender::File> creates a new file,
its permissions are set to C<rw-r--r-->. Why? Because your
environment's I<umask> most likely defaults to
C<0022>, that's the standard setting.
What's a I<umask>, you're asking? It's a template that's applied to
the permissions of all newly created files. While calls like
C<open(FILE, "E<gt>foo")> will always try to create files in C<rw-rw-rw-
> mode, the system will apply the current I<umask> template to
determine the final permission setting. I<umask> is a bit mask that's
inverted and then applied to the requested permission setting, using a
bitwise AND:
$request_permission &~ $umask
So, a I<umask> setting of 0000 (the leading 0 simply indicates an
octal value) will create files in C<rw-rw-rw-> mode, a setting of 0277
will use C<r-------->, and the standard 0022 will use C<rw-r--r-->.
As an example, if you want your log files to be created with
C<rw-r--rw-> permissions, use a I<umask> of C<0020> before
calling Log::Log4perl->init():
use Log::Log4perl;
umask 0020;
# Creates log.out in rw-r--rw mode
Log::Log4perl->init(\ q{
log4perl.logger = WARN, File
log4perl.appender.File = Log::Log4perl::Appender::File
log4perl.appender.File.filename = log.out
log4perl.appender.File.layout = SimpleLayout
});
=head2 Using Log4perl in an END block causes a problem!
It's not easy to get to this error, but if you write something like
END { Log::Log4perl::get_logger()->debug("Hey there."); }
use Log::Log4perl qw(:easy);
Log::Log4perl->easy_init($DEBUG);
it won't work. The reason is that C<Log::Log4perl> defines an
END block that cleans up all loggers. And perl will run END blocks
in the reverse order as they're encountered in the compile phase,
so in the scenario above, the END block will run I<after> Log4perl
has cleaned up its loggers.
Placing END blocks using Log4perl I<after>
a C<use Log::Log4perl> statement fixes the problem:
use Log::Log4perl qw(:easy);
Log::Log4perl->easy_init($DEBUG);
END { Log::Log4perl::get_logger()->debug("Hey there."); }
In this scenario, the shown END block is executed I<before> Log4perl
cleans up and the debug message will be processed properly.
=head2 Help! My appender is throwing a "Wide character in print" warning!
This warning shows up when Unicode strings are printed without
precautions. The warning goes away if the complaining appender is
set to utf-8 mode:
# Either in the log4perl configuration file:
log4perl.appender.Logfile.filename = test.log
log4perl.appender.Logfile.utf8 = 1
# Or, in easy mode:
Log::Log4perl->easy_init( {
level => $DEBUG,
file => ":utf8> test.log"
} );
If the complaining appender is a screen appender, set its C<utf8> option:
log4perl.appender.Screen.stderr = 1
log4perl.appender.Screen.utf8 = 1
Alternatively, C<binmode> does the trick:
# Either STDOUT ...
binmode(STDOUT, ":utf8);
# ... or STDERR.
binmode(STDERR, ":utf8);
Some background on this: Perl's strings are either byte strings or
Unicode strings. C<"Mike"> is a byte string.
C<"\x{30DE}\x{30A4}\x{30AF}"> is a Unicode string. Unicode strings are
marked specially and are UTF-8 encoded internally.
If you print a byte string to STDOUT,
all is well, because STDOUT is by default set to byte mode. However,
if you print a Unicode string to STDOUT without precautions, C<perl>
will try to transform the Unicode string back to a byte string before
printing it out. This is troublesome if the Unicode string contains
'wide' characters which can't be represented in Latin-1.
For example, if you create a Unicode string with three japanese Katakana
characters as in
perl -le 'print "\x{30DE}\x{30A4}\x{30AF}"'
(coincidentally pronounced Ma-i-ku, the japanese pronounciation of
"Mike"), STDOUT is in byte mode and the warning
Wide character in print at ./script.pl line 14.
appears. Setting STDOUT to UTF-8 mode as in
perl -le 'binmode(STDOUT, ":utf8"); print "\x{30DE}\x{30A4}\x{30AF}"'
will silently print the Unicode string to STDOUT in UTF-8. To see the
characters printed, you'll need a UTF-8 terminal with a font including
japanese Katakana characters.
=head2 How can I send errors to the screen, and debug messages to a file?
Let's assume you want to maintain a detailed DEBUG output in a file
and only messages of level ERROR and higher should be printed on the
screen. Often times, developers come up with something like this:
# Wrong!!!
log4perl.logger = DEBUG, FileApp
log4perl.logger = ERROR, ScreenApp
# Wrong!!!
This won't work, however. Logger definitions aren't additive, and the
second statement will overwrite the first one. Log4perl versions
below 1.04 were silently accepting this, leaving people confused why
it wouldn't work as expected.
As of 1.04, this will throw a I<fatal error> to notify the user of
the problem.
What you want to do instead, is this:
log4perl.logger = DEBUG, FileApp, ScreenApp
log4perl.appender.FileApp = Log::Log4perl::Appender::File
log4perl.appender.FileApp.filename = test.log
log4perl.appender.FileApp.layout = SimpleLayout
log4perl.appender.ScreenApp = Log::Log4perl::Appender::Screen
log4perl.appender.ScreenApp.stderr = 0
log4perl.appender.ScreenApp.layout = SimpleLayout
### limiting output to ERROR messages
log4perl.appender.ScreenApp.Threshold = ERROR
###
Note that without the second appender's C<Threshold> setting, both appenders
would receive all messages prioritized DEBUG and higher. With the
threshold set to ERROR, the second appender will filter the messages
as required.
=head2 Where should I put my logfiles?
Your log files may go anywhere you want them, but the effective
user id of the calling process must have write access.
If the log file doesn't exist at program start, Log4perl's file appender
will create it. For this, it needs write access to the directory where
the new file will be located in. If the log file already exists at startup,
the process simply needs write access to the file. Note that it will
need write access to the file's directory if you're encountering situations
where the logfile gets recreated, e.g. during log rotation.
If Log::Log4perl is used by a web server application (e.g. in a CGI script
or mod_perl), then the webserver's user (usually C<nobody> or C<www>)
must have the permissions mentioned above.
To prepare your web server to use log4perl, we'd recommend:
webserver:~$ su -
webserver:~# mkdir /var/log/cgiapps
webserver:~# chown nobody:root /var/log/cgiapps/
webserver:~# chown nobody:root -R /var/log/cgiapps/
webserver:~# chmod 02755 -R /var/log/cgiapps/
Then set your /etc/log4perl.conf file to include:
log4perl.appender.FileAppndr1.filename =
/var/log/cgiapps/<app-name>.log
=head2 How can my file appender deal with disappearing log files?
The file appender that comes with Log4perl, L<Log::Log4perl::Appender::File>,
will open a specified log file at initialization time and will
keep writing to it via a file handle.
In case the associated file goes way, messages written by a
long-running process will still be written
to the file handle. In case the file has been moved to a different
location on the same file system, the writer will keep writing to
it under the new filename. In case the file has been removed from
the file system, the log messages will end up in nowhere land. This
is not a bug in Log4perl, this is how Unix works. There is
no error message in this case, because the writer has no idea that
the file handle is not associated with a visible file.
To prevent the loss of log messages when log files disappear, the
file appender's C<recreate> option needs to be set to a true value:
log4perl.appender.Logfile.recreate = 1
This will instruct the file appender to check in regular intervals
(default: 30 seconds) if the log file is still there. If it finds
out that the file is missing, it will recreate it.
Continuously checking if the log file still exists is fairly
expensive. For this reason it is only performed every 30 seconds. To
change this interval, the option C<recreate_check_interval> can be set
to the number of seconds between checks. In the extreme case where the
check should be performed before every write, it can even be set to 0:
log4perl.appender.Logfile.recreate = 1
log4perl.appender.Logfile.recreate_check_interval = 0
To avoid having to check the file system so frequently, a signal
handler can be set up:
log4perl.appender.Logfile.recreate = 1
log4perl.appender.Logfile.recreate_check_signal = USR1
This will install a signal handler which will recreate a missing log file
immediatly when it receives the defined signal.
Note that the init_and_watch() method for Log4perl's initialization
can also be instructed to install a signal handler, usually using the
HUP signal. Make sure to use a different signal if you're using both
of them at the same time.
=head2 How can I rotate a logfile with newsyslog?
Here's a few things that need to be taken care of when using the popular
log file rotating utilty C<newsyslog>
(http://www.courtesan.com/newsyslog) with Log4perl's file appender
in long-running processes.
For example, with a newsyslog configuration like
# newsyslog.conf
/tmp/test.log 666 12 5 * B
and a call to
# newsyslog -f /path/to/newsyslog.conf
C<newsyslog> will take action if C</tmp/test.log> is larger than the
specified 5K in size. It will move the current log file C</tmp/test.log> to
C</tmp/test.log.0> and create a new and empty C</tmp/test.log> with
the specified permissions (this is why C<newsyslog> needs to run as root).
An already existing C</tmp/test.log.0> would be moved to
C</tmp/test.log.1>, C</tmp/test.log.1> to C</tmp/test.log.2>, and so
forth, for every one of a max number of 12 archived logfiles that have
been configured in C<newsyslog.conf>.
Although a new file has been created, from Log4perl's appender's point
of view, this situation is identical to the one described in the
previous FAQ entry, labeled C<How can my file appender deal with
disappearing log files>.
To make sure that log messages are written to the new log file and not
to an archived one or end up in nowhere land,
the appender's C<recreate> and C<recreate_check_interval> have to be
configured to deal with the 'disappearing' log file.
The situation gets interesting when C<newsyslog>'s option
to compress archived log files is enabled. This causes the
original log file not to be moved, but to disappear. If the
file appender isn't configured to recreate the logfile in this situation,
log messages will actually be lost without warning. This also
applies for the short time frame of C<recreate_check_interval> seconds
in between the recreator's file checks.
To make sure that no messages get lost, one option is to set the
interval to
log4perl.appender.Logfile.recreate_check_interval = 0
However, this is fairly expensive. A better approach is to define
a signal handler:
log4perl.appender.Logfile.recreate = 1
log4perl.appender.Logfile.recreate_check_signal = USR1
log4perl.appender.Logfile.recreate_pid_write = /tmp/myappid
As a service for C<newsyslog> users, Log4perl's file appender writes
the current process ID to a PID file specified by the C<recreate_pid_write>
option. C<newsyslog> then needs to be configured as in
# newsyslog.conf configuration for compressing archive files and
# sending a signal to the Log4perl-enabled application
/tmp/test.log 666 12 5 * B /tmp/myappid 30
to send the defined signal (30, which is USR1 on FreeBSD) to the
application process at rotation time. Note that the signal number
is different on Linux, where USR1 denotes as 10. Check C<man signal>
for details.
=head2 How can a process under user id A log to a file under user id B?
This scenario often occurs in configurations where processes run under
various user IDs but need to write to a log file under a fixed, but
different user id.
With a traditional file appender, the log file will probably be created
under one user's id and appended to under a different user's id. With
a typical umask of 0002, the file will be created with -rw-rw-r--
permissions. If a user who's not in the first user's group
subsequently appends to the log file, it will fail because of a
permission problem.
Two potential solutions come to mind:
=over 4
=item *
Creating the file with a umask of 0000 will allow all users to append
to the log file. Log4perl's file appender C<Log::Log4perl::Appender::File>
has an C<umask> option that can be set to support this:
log4perl.appender.File = Log::Log4perl::Appender::File
log4perl.appender.File.umask = sub { 0000 };
This way, the log file will be created with -rw-rw-rw- permissions and
therefore has world write permissions. This might open up the logfile
for unwanted manipulations by arbitrary users, though.
=item *
Running the process under an effective user id of C<root> will allow
it to write to the log file, no matter who started the process.
However, this is not a good idea, because of security concerns.
=back
Luckily, under Unix, there's the syslog daemon which runs as root and
takes log requests from user processes over a socket and writes them
to log files as configured in C</etc/syslog.conf>.
By modifying C</etc/syslog.conf> and HUPing the syslog daemon, you can
configure new log files:
# /etc/syslog.conf
...
user.* /some/path/file.log
Using the C<Log::Dispatch::Syslog> appender, which comes with the
C<Log::Log4perl> distribution, you can then send messages via syslog:
use Log::Log4perl qw(:easy);
Log::Log4perl->init(\<<EOT);
log4perl.logger = DEBUG, app
log4perl.appender.app=Log::Dispatch::Syslog
log4perl.appender.app.Facility=user
log4perl.appender.app.layout=SimpleLayout
EOT
# Writes to /some/path/file.log
ERROR "Message!";
This way, the syslog daemon will solve the permission problem.
Note that while it is possible to use syslog() without Log4perl (syslog
supports log levels, too), traditional syslog setups have a
significant drawback.
Without Log4perl's ability to activate logging in only specific
parts of a system, complex systems will trigger log events all over
the place and slow down execution to a crawl at high debug levels.
Remote-controlling logging in the hierarchical parts of an application
via Log4perl's categories is one of its most distinguished features.
It allows for enabling high debug levels in specified areas without
noticeable performance impact.
=head2 I want to use UTC instead of the local time!
If a layout defines a date, Log::Log4perl uses local time to populate it.
If you want UTC instead, set
$Log::Log4perl::DateFormat::GMTIME = 1;
in your program before the first log statement.
=head2 Can Log4perl intercept messages written to a filehandle?
You have a function that prints to a filehandle. You want to tie
into that filehandle and forward all arriving messages to a
Log4perl logger.
First, let's write a package that ties a file handle and forwards it
to a Log4perl logger:
package FileHandleLogger;
use Log::Log4perl qw(:levels get_logger);
sub TIEHANDLE {
my($class, %options) = @_;
my $self = {
level => $DEBUG,
category => '',
%options
};
$self->{logger} = get_logger($self->{category}),
bless $self, $class;
}
sub PRINT {
my($self, @rest) = @_;
$Log::Log4perl::caller_depth++;
$self->{logger}->log($self->{level}, @rest);
$Log::Log4perl::caller_depth--;
}
sub PRINTF {
my($self, $fmt, @rest) = @_;
$Log::Log4perl::caller_depth++;
$self->PRINT(sprintf($fmt, @rest));
$Log::Log4perl::caller_depth--;
}
1;
Now, if you have a function like
sub function_printing_to_fh {
my($fh) = @_;
printf $fh "Hi there!\n";
}
which takes a filehandle and prints something to it, it can be used
with Log4perl:
use Log::Log4perl qw(:easy);
usa FileHandleLogger;
Log::Log4perl->easy_init($DEBUG);
tie *SOMEHANDLE, 'FileHandleLogger' or
die "tie failed ($!)";
function_printing_to_fh(*SOMEHANDLE);
# prints "2007/03/22 21:43:30 Hi there!"
If you want, you can even specify a different log level or category:
tie *SOMEHANDLE, 'FileHandleLogger',
level => $INFO, category => "Foo::Bar" or die "tie failed ($!)";
=head2 I want multiline messages rendered line-by-line!
With the standard C<PatternLayout>, if you send a multiline message to
an appender as in
use Log::Log4perl qw(:easy);
Log
it gets rendered this way:
2007/04/04 23:23:39 multi
line
message
If you want each line to be rendered separately according to
the layout use C<Log::Log4perl::Layout::PatternLayout::Multiline>:
use Log::Log4perl qw(:easy);
Log::Log4perl->init(\<<EOT);
log4perl.category = DEBUG, Screen
log4perl.appender.Screen = Log::Log4perl::Appender::Screen
log4perl.appender.Screen.layout = \\
Log::Log4perl::Layout::PatternLayout::Multiline
log4perl.appender.Screen.layout.ConversionPattern = %d %m %n
EOT
DEBUG "some\nmultiline\nmessage";
and you'll get
2007/04/04 23:23:39 some
2007/04/04 23:23:39 multiline
2007/04/04 23:23:39 message
instead.
=head2 I'm on Windows and I'm getting all these 'redefined' messages!
If you're on Windows and are getting warning messages like
Constant subroutine Log::Log4perl::_INTERNAL_DEBUG redefined at
C:/Programme/Perl/lib/constant.pm line 103.
Subroutine import redefined at
C:/Programme/Perl/site/lib/Log/Log4Perl.pm line 69.
Subroutine initialized redefined at
C:/Programme/Perl/site/lib/Log/Log4Perl.pm line 207.
then chances are that you're using 'Log::Log4Perl' (wrong uppercase P)
instead of the correct 'Log::Log4perl'. Perl on Windows doesn't
handle this error well and spits out a slew of confusing warning
messages. But now you know, just use the correct module name and
you'll be fine.
=head2 Log4perl complains that no initialization happened during shutdown!
If you're using Log4perl log commands in DESTROY methods of your objects,
you might see confusing messages like
Log4perl: Seems like no initialization happened. Forgot to call init()?
Use of uninitialized value in subroutine entry at
/home/y/lib/perl5/site_perl/5.6.1/Log/Log4perl.pm line 134 during global
destruction. (in cleanup) Undefined subroutine &main:: called at
/home/y/lib/perl5/site_perl/5.6.1/Log/Log4perl.pm line 134 during global
destruction.
when the program shuts down. What's going on?
This phenomenon happens if you have circular references in your objects,
which perl can't clean up when an object goes out of scope but waits
until global destruction instead. At this time, however, Log4perl has
already shut down, so you can't use it anymore.
For example, here's a simple class which uses a logger in its DESTROY
method:
package A;
use Log::Log4perl qw(:easy);
sub new { bless {}, shift }
sub DESTROY { DEBUG "Waaah!"; }
Now, if the main program creates a self-referencing object, like in
package main;
use Log::Log4perl qw(:easy);
Log::Log4perl->easy_init($DEBUG);
my $a = A->new();
$a->{selfref} = $a;
then you'll see the error message shown above during global destruction.
How to tackle this problem?
First, you should clean up your circular references before global
destruction. They will not only cause objects to be destroyed in an order
that's hard to predict, but also eat up memory until the program shuts
down.
So, the program above could easily be fixed by putting
$a->{selfref} = undef;
at the end or in an END handler. If that's hard to do, use weak references:
package main;
use Scalar::Util qw(weaken);
use Log::Log4perl qw(:easy);
Log::Log4perl->easy_init($DEBUG);
my $a = A->new();
$a->{selfref} = weaken $a;
This allows perl to clean up the circular reference when the object
goes out of scope, and doesn't wait until global destruction.
=head2 How can I access POE heap values from Log4perl's layout?
POE is a framework for creating multitasked applications running in a
single process and a single thread. POE's threads equivalents are
'sessions' and since they run quasi-simultaneously, you can't use
Log4perl's global NDC/MDC to hold session-specific data.
However, POE already maintains a data store for every session. It is called
'heap' and is just a hash storing session-specific data in key-value pairs.
To access this per-session heap data from a Log4perl layout, define a
custom cspec and reference it with the newly defined pattern in the layout:
use strict;
use POE;
use Log::Log4perl qw(:easy);
Log::Log4perl->init( \ q{
log4perl.logger = DEBUG, Screen
log4perl.appender.Screen = Log::Log4perl::Appender::Screen
log4perl.appender.Screen.layout = PatternLayout
log4perl.appender.Screen.layout.ConversionPattern = %U %m%n
log4perl.PatternLayout.cspec.U = \
sub { POE::Kernel->get_active_session->get_heap()->{ user } }
} );
for (qw( Huey Lewey Dewey )) {
POE::Session->create(
inline_states => {
_start => sub {
$_[HEAP]->{user} = $_;
POE::Kernel->yield('hello');
},
hello => sub {
DEBUG "I'm here now";
}
}
);
}
POE::Kernel->run();
exit;
The code snippet above defines a new layout placeholder (called
'cspec' in Log4perl) %U which calls a subroutine, retrieves the active
session, gets its heap and looks up the entry specified ('user').
Starting with Log::Log4perl 1.20, cspecs also support parameters in
curly braces, so you can say
log4perl.appender.Screen.layout.ConversionPattern = %U{user} %U{id} %m%n
log4perl.PatternLayout.cspec.U = \
sub { POE::Kernel->get_active_session-> \
get_heap()->{ $_[0]->{curlies} } }
and print the POE session heap entries 'user' and 'id' with every logged
message. For more details on cpecs, read the PatternLayout manual.
=head2 I want to print something unconditionally!
Sometimes it's a script that's supposed to log messages regardless if
Log4perl has been initialized or not. Or there's a logging statement that's
not going to be suppressed under any circumstances -- many people want to
have the final word, make the executive decision, because it seems like
the only logical choice.
But think about it:
First off, if a messages is supposed to be printed, where is it supposed
to end up at? STDOUT? STDERR? And are you sure you want to set in stone
that this message needs to be printed, while someone else might
find it annoying and wants to get rid of it?
The truth is, there's always going to be someone who wants to log a
messages at all cost, but also another person who wants to suppress it
with equal vigilance. There's no good way to serve these two conflicting
desires, someone will always want to win at the cost of leaving
the other party dissappointed.
So, the best Log4perl offers is the ALWAYS level for a message that even
fires if the system log level is set to $OFF:
use Log::Log4perl qw(:easy);
Log::Log4perl->easy_init( $OFF );
ALWAYS "This gets logged always. Well, almost always";
The logger won't fire, though, if Log4perl hasn't been initialized or
if someone defines a custom log hurdle that's higher than $OFF.
Bottom line: Leave the setting of the logging level to the initial Perl
script -- let their owners decided what they want, no matter how tempting
it may be to decide it for them.
=cut
=head1 SEE ALSO
Log::Log4perl
=head1 LICENSE
Copyright 2002-2013 by Mike Schilli E<lt>m@perlmeister.comE<gt>
and Kevin Goess E<lt>cpan@goess.orgE<gt>.
This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.
=head1 AUTHOR
Please contribute patches to the project on Github:
http://github.com/mschilli/log4perl
Send bug reports or requests for enhancements to the authors via our
MAILING LIST (questions, bug reports, suggestions/patches):
log4perl-devel@lists.sourceforge.net
Authors (please contact them via the list above, not directly):
Mike Schilli <m@perlmeister.com>,
Kevin Goess <cpan@goess.org>
Contributors (in alphabetical order):
Ateeq Altaf, Cory Bennett, Jens Berthold, Jeremy Bopp, Hutton
Davidson, Chris R. Donnelly, Matisse Enzer, Hugh Esco, Anthony
Foiani, James FitzGibbon, Carl Franks, Dennis Gregorovic, Andy
Grundman, Paul Harrington, Alexander Hartmaier David Hull,
Robert Jacobson, Jason Kohles, Jeff Macdonald, Markus Peter,
Brett Rann, Peter Rabbitson, Erik Selberg, Aaron Straup Cope,
Lars Thegler, David Viner, Mac Yang.
|