├── .gitignore ├── LICENSE.txt ├── README.md ├── SimpleLog.py ├── __init__.py ├── __pkginfo__.py ├── docs ├── Makefile ├── make.bat └── source │ ├── conf.py │ └── index.rst └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.log 3 | .DS_Store 4 | MANIFEST.in 5 | dist/ 6 | docs/.DS_Store 7 | docs/build/ 8 | pysimplelog.egg-info/ 9 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | GNU AFFERO GENERAL PUBLIC LICENSE 2 | Version 3, 19 November 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU Affero General Public License is a free, copyleft license for 11 | software and other kinds of works, specifically designed to ensure 12 | cooperation with the community in the case of network server software. 13 | 14 | The licenses for most software and other practical works are designed 15 | to take away your freedom to share and change the works. By contrast, 16 | our General Public Licenses are intended to guarantee your freedom to 17 | share and change all versions of a program--to make sure it remains free 18 | software for all its users. 19 | 20 | When we speak of free software, we are referring to freedom, not 21 | price. Our General Public Licenses are designed to make sure that you 22 | have the freedom to distribute copies of free software (and charge for 23 | them if you wish), that you receive source code or can get it if you 24 | want it, that you can change the software or use pieces of it in new 25 | free programs, and that you know you can do these things. 26 | 27 | Developers that use our General Public Licenses protect your rights 28 | with two steps: (1) assert copyright on the software, and (2) offer 29 | you this License which gives you legal permission to copy, distribute 30 | and/or modify the software. 31 | 32 | A secondary benefit of defending all users' freedom is that 33 | improvements made in alternate versions of the program, if they 34 | receive widespread use, become available for other developers to 35 | incorporate. Many developers of free software are heartened and 36 | encouraged by the resulting cooperation. However, in the case of 37 | software used on network servers, this result may fail to come about. 38 | The GNU General Public License permits making a modified version and 39 | letting the public access it on a server without ever releasing its 40 | source code to the public. 41 | 42 | The GNU Affero General Public License is designed specifically to 43 | ensure that, in such cases, the modified source code becomes available 44 | to the community. It requires the operator of a network server to 45 | provide the source code of the modified version running there to the 46 | users of that server. Therefore, public use of a modified version, on 47 | a publicly accessible server, gives the public access to the source 48 | code of the modified version. 49 | 50 | An older license, called the Affero General Public License and 51 | published by Affero, was designed to accomplish similar goals. This is 52 | a different license, not a version of the Affero GPL, but Affero has 53 | released a new version of the Affero GPL which permits relicensing under 54 | this license. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | TERMS AND CONDITIONS 60 | 61 | 0. Definitions. 62 | 63 | "This License" refers to version 3 of the GNU Affero General Public License. 64 | 65 | "Copyright" also means copyright-like laws that apply to other kinds of 66 | works, such as semiconductor masks. 67 | 68 | "The Program" refers to any copyrightable work licensed under this 69 | License. Each licensee is addressed as "you". "Licensees" and 70 | "recipients" may be individuals or organizations. 71 | 72 | To "modify" a work means to copy from or adapt all or part of the work 73 | in a fashion requiring copyright permission, other than the making of an 74 | exact copy. The resulting work is called a "modified version" of the 75 | earlier work or a work "based on" the earlier work. 76 | 77 | A "covered work" means either the unmodified Program or a work based 78 | on the Program. 79 | 80 | To "propagate" a work means to do anything with it that, without 81 | permission, would make you directly or secondarily liable for 82 | infringement under applicable copyright law, except executing it on a 83 | computer or modifying a private copy. Propagation includes copying, 84 | distribution (with or without modification), making available to the 85 | public, and in some countries other activities as well. 86 | 87 | To "convey" a work means any kind of propagation that enables other 88 | parties to make or receive copies. Mere interaction with a user through 89 | a computer network, with no transfer of a copy, is not conveying. 90 | 91 | An interactive user interface displays "Appropriate Legal Notices" 92 | to the extent that it includes a convenient and prominently visible 93 | feature that (1) displays an appropriate copyright notice, and (2) 94 | tells the user that there is no warranty for the work (except to the 95 | extent that warranties are provided), that licensees may convey the 96 | work under this License, and how to view a copy of this License. If 97 | the interface presents a list of user commands or options, such as a 98 | menu, a prominent item in the list meets this criterion. 99 | 100 | 1. Source Code. 101 | 102 | The "source code" for a work means the preferred form of the work 103 | for making modifications to it. "Object code" means any non-source 104 | form of a work. 105 | 106 | A "Standard Interface" means an interface that either is an official 107 | standard defined by a recognized standards body, or, in the case of 108 | interfaces specified for a particular programming language, one that 109 | is widely used among developers working in that language. 110 | 111 | The "System Libraries" of an executable work include anything, other 112 | than the work as a whole, that (a) is included in the normal form of 113 | packaging a Major Component, but which is not part of that Major 114 | Component, and (b) serves only to enable use of the work with that 115 | Major Component, or to implement a Standard Interface for which an 116 | implementation is available to the public in source code form. A 117 | "Major Component", in this context, means a major essential component 118 | (kernel, window system, and so on) of the specific operating system 119 | (if any) on which the executable work runs, or a compiler used to 120 | produce the work, or an object code interpreter used to run it. 121 | 122 | The "Corresponding Source" for a work in object code form means all 123 | the source code needed to generate, install, and (for an executable 124 | work) run the object code and to modify the work, including scripts to 125 | control those activities. However, it does not include the work's 126 | System Libraries, or general-purpose tools or generally available free 127 | programs which are used unmodified in performing those activities but 128 | which are not part of the work. For example, Corresponding Source 129 | includes interface definition files associated with source files for 130 | the work, and the source code for shared libraries and dynamically 131 | linked subprograms that the work is specifically designed to require, 132 | such as by intimate data communication or control flow between those 133 | subprograms and other parts of the work. 134 | 135 | The Corresponding Source need not include anything that users 136 | can regenerate automatically from other parts of the Corresponding 137 | Source. 138 | 139 | The Corresponding Source for a work in source code form is that 140 | same work. 141 | 142 | 2. Basic Permissions. 143 | 144 | All rights granted under this License are granted for the term of 145 | copyright on the Program, and are irrevocable provided the stated 146 | conditions are met. This License explicitly affirms your unlimited 147 | permission to run the unmodified Program. The output from running a 148 | covered work is covered by this License only if the output, given its 149 | content, constitutes a covered work. This License acknowledges your 150 | rights of fair use or other equivalent, as provided by copyright law. 151 | 152 | You may make, run and propagate covered works that you do not 153 | convey, without conditions so long as your license otherwise remains 154 | in force. You may convey covered works to others for the sole purpose 155 | of having them make modifications exclusively for you, or provide you 156 | with facilities for running those works, provided that you comply with 157 | the terms of this License in conveying all material for which you do 158 | not control copyright. Those thus making or running the covered works 159 | for you must do so exclusively on your behalf, under your direction 160 | and control, on terms that prohibit them from making any copies of 161 | your copyrighted material outside their relationship with you. 162 | 163 | Conveying under any other circumstances is permitted solely under 164 | the conditions stated below. Sublicensing is not allowed; section 10 165 | makes it unnecessary. 166 | 167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 168 | 169 | No covered work shall be deemed part of an effective technological 170 | measure under any applicable law fulfilling obligations under article 171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 172 | similar laws prohibiting or restricting circumvention of such 173 | measures. 174 | 175 | When you convey a covered work, you waive any legal power to forbid 176 | circumvention of technological measures to the extent such circumvention 177 | is effected by exercising rights under this License with respect to 178 | the covered work, and you disclaim any intention to limit operation or 179 | modification of the work as a means of enforcing, against the work's 180 | users, your or third parties' legal rights to forbid circumvention of 181 | technological measures. 182 | 183 | 4. Conveying Verbatim Copies. 184 | 185 | You may convey verbatim copies of the Program's source code as you 186 | receive it, in any medium, provided that you conspicuously and 187 | appropriately publish on each copy an appropriate copyright notice; 188 | keep intact all notices stating that this License and any 189 | non-permissive terms added in accord with section 7 apply to the code; 190 | keep intact all notices of the absence of any warranty; and give all 191 | recipients a copy of this License along with the Program. 192 | 193 | You may charge any price or no price for each copy that you convey, 194 | and you may offer support or warranty protection for a fee. 195 | 196 | 5. Conveying Modified Source Versions. 197 | 198 | You may convey a work based on the Program, or the modifications to 199 | produce it from the Program, in the form of source code under the 200 | terms of section 4, provided that you also meet all of these conditions: 201 | 202 | a) The work must carry prominent notices stating that you modified 203 | it, and giving a relevant date. 204 | 205 | b) The work must carry prominent notices stating that it is 206 | released under this License and any conditions added under section 207 | 7. This requirement modifies the requirement in section 4 to 208 | "keep intact all notices". 209 | 210 | c) You must license the entire work, as a whole, under this 211 | License to anyone who comes into possession of a copy. This 212 | License will therefore apply, along with any applicable section 7 213 | additional terms, to the whole of the work, and all its parts, 214 | regardless of how they are packaged. This License gives no 215 | permission to license the work in any other way, but it does not 216 | invalidate such permission if you have separately received it. 217 | 218 | d) If the work has interactive user interfaces, each must display 219 | Appropriate Legal Notices; however, if the Program has interactive 220 | interfaces that do not display Appropriate Legal Notices, your 221 | work need not make them do so. 222 | 223 | A compilation of a covered work with other separate and independent 224 | works, which are not by their nature extensions of the covered work, 225 | and which are not combined with it such as to form a larger program, 226 | in or on a volume of a storage or distribution medium, is called an 227 | "aggregate" if the compilation and its resulting copyright are not 228 | used to limit the access or legal rights of the compilation's users 229 | beyond what the individual works permit. Inclusion of a covered work 230 | in an aggregate does not cause this License to apply to the other 231 | parts of the aggregate. 232 | 233 | 6. Conveying Non-Source Forms. 234 | 235 | You may convey a covered work in object code form under the terms 236 | of sections 4 and 5, provided that you also convey the 237 | machine-readable Corresponding Source under the terms of this License, 238 | in one of these ways: 239 | 240 | a) Convey the object code in, or embodied in, a physical product 241 | (including a physical distribution medium), accompanied by the 242 | Corresponding Source fixed on a durable physical medium 243 | customarily used for software interchange. 244 | 245 | b) Convey the object code in, or embodied in, a physical product 246 | (including a physical distribution medium), accompanied by a 247 | written offer, valid for at least three years and valid for as 248 | long as you offer spare parts or customer support for that product 249 | model, to give anyone who possesses the object code either (1) a 250 | copy of the Corresponding Source for all the software in the 251 | product that is covered by this License, on a durable physical 252 | medium customarily used for software interchange, for a price no 253 | more than your reasonable cost of physically performing this 254 | conveying of source, or (2) access to copy the 255 | Corresponding Source from a network server at no charge. 256 | 257 | c) Convey individual copies of the object code with a copy of the 258 | written offer to provide the Corresponding Source. This 259 | alternative is allowed only occasionally and noncommercially, and 260 | only if you received the object code with such an offer, in accord 261 | with subsection 6b. 262 | 263 | d) Convey the object code by offering access from a designated 264 | place (gratis or for a charge), and offer equivalent access to the 265 | Corresponding Source in the same way through the same place at no 266 | further charge. You need not require recipients to copy the 267 | Corresponding Source along with the object code. If the place to 268 | copy the object code is a network server, the Corresponding Source 269 | may be on a different server (operated by you or a third party) 270 | that supports equivalent copying facilities, provided you maintain 271 | clear directions next to the object code saying where to find the 272 | Corresponding Source. Regardless of what server hosts the 273 | Corresponding Source, you remain obligated to ensure that it is 274 | available for as long as needed to satisfy these requirements. 275 | 276 | e) Convey the object code using peer-to-peer transmission, provided 277 | you inform other peers where the object code and Corresponding 278 | Source of the work are being offered to the general public at no 279 | charge under subsection 6d. 280 | 281 | A separable portion of the object code, whose source code is excluded 282 | from the Corresponding Source as a System Library, need not be 283 | included in conveying the object code work. 284 | 285 | A "User Product" is either (1) a "consumer product", which means any 286 | tangible personal property which is normally used for personal, family, 287 | or household purposes, or (2) anything designed or sold for incorporation 288 | into a dwelling. In determining whether a product is a consumer product, 289 | doubtful cases shall be resolved in favor of coverage. For a particular 290 | product received by a particular user, "normally used" refers to a 291 | typical or common use of that class of product, regardless of the status 292 | of the particular user or of the way in which the particular user 293 | actually uses, or expects or is expected to use, the product. A product 294 | is a consumer product regardless of whether the product has substantial 295 | commercial, industrial or non-consumer uses, unless such uses represent 296 | the only significant mode of use of the product. 297 | 298 | "Installation Information" for a User Product means any methods, 299 | procedures, authorization keys, or other information required to install 300 | and execute modified versions of a covered work in that User Product from 301 | a modified version of its Corresponding Source. The information must 302 | suffice to ensure that the continued functioning of the modified object 303 | code is in no case prevented or interfered with solely because 304 | modification has been made. 305 | 306 | If you convey an object code work under this section in, or with, or 307 | specifically for use in, a User Product, and the conveying occurs as 308 | part of a transaction in which the right of possession and use of the 309 | User Product is transferred to the recipient in perpetuity or for a 310 | fixed term (regardless of how the transaction is characterized), the 311 | Corresponding Source conveyed under this section must be accompanied 312 | by the Installation Information. But this requirement does not apply 313 | if neither you nor any third party retains the ability to install 314 | modified object code on the User Product (for example, the work has 315 | been installed in ROM). 316 | 317 | The requirement to provide Installation Information does not include a 318 | requirement to continue to provide support service, warranty, or updates 319 | for a work that has been modified or installed by the recipient, or for 320 | the User Product in which it has been modified or installed. Access to a 321 | network may be denied when the modification itself materially and 322 | adversely affects the operation of the network or violates the rules and 323 | protocols for communication across the network. 324 | 325 | Corresponding Source conveyed, and Installation Information provided, 326 | in accord with this section must be in a format that is publicly 327 | documented (and with an implementation available to the public in 328 | source code form), and must require no special password or key for 329 | unpacking, reading or copying. 330 | 331 | 7. Additional Terms. 332 | 333 | "Additional permissions" are terms that supplement the terms of this 334 | License by making exceptions from one or more of its conditions. 335 | Additional permissions that are applicable to the entire Program shall 336 | be treated as though they were included in this License, to the extent 337 | that they are valid under applicable law. If additional permissions 338 | apply only to part of the Program, that part may be used separately 339 | under those permissions, but the entire Program remains governed by 340 | this License without regard to the additional permissions. 341 | 342 | When you convey a copy of a covered work, you may at your option 343 | remove any additional permissions from that copy, or from any part of 344 | it. (Additional permissions may be written to require their own 345 | removal in certain cases when you modify the work.) You may place 346 | additional permissions on material, added by you to a covered work, 347 | for which you have or can give appropriate copyright permission. 348 | 349 | Notwithstanding any other provision of this License, for material you 350 | add to a covered work, you may (if authorized by the copyright holders of 351 | that material) supplement the terms of this License with terms: 352 | 353 | a) Disclaiming warranty or limiting liability differently from the 354 | terms of sections 15 and 16 of this License; or 355 | 356 | b) Requiring preservation of specified reasonable legal notices or 357 | author attributions in that material or in the Appropriate Legal 358 | Notices displayed by works containing it; or 359 | 360 | c) Prohibiting misrepresentation of the origin of that material, or 361 | requiring that modified versions of such material be marked in 362 | reasonable ways as different from the original version; or 363 | 364 | d) Limiting the use for publicity purposes of names of licensors or 365 | authors of the material; or 366 | 367 | e) Declining to grant rights under trademark law for use of some 368 | trade names, trademarks, or service marks; or 369 | 370 | f) Requiring indemnification of licensors and authors of that 371 | material by anyone who conveys the material (or modified versions of 372 | it) with contractual assumptions of liability to the recipient, for 373 | any liability that these contractual assumptions directly impose on 374 | those licensors and authors. 375 | 376 | All other non-permissive additional terms are considered "further 377 | restrictions" within the meaning of section 10. If the Program as you 378 | received it, or any part of it, contains a notice stating that it is 379 | governed by this License along with a term that is a further 380 | restriction, you may remove that term. If a license document contains 381 | a further restriction but permits relicensing or conveying under this 382 | License, you may add to a covered work material governed by the terms 383 | of that license document, provided that the further restriction does 384 | not survive such relicensing or conveying. 385 | 386 | If you add terms to a covered work in accord with this section, you 387 | must place, in the relevant source files, a statement of the 388 | additional terms that apply to those files, or a notice indicating 389 | where to find the applicable terms. 390 | 391 | Additional terms, permissive or non-permissive, may be stated in the 392 | form of a separately written license, or stated as exceptions; 393 | the above requirements apply either way. 394 | 395 | 8. Termination. 396 | 397 | You may not propagate or modify a covered work except as expressly 398 | provided under this License. Any attempt otherwise to propagate or 399 | modify it is void, and will automatically terminate your rights under 400 | this License (including any patent licenses granted under the third 401 | paragraph of section 11). 402 | 403 | However, if you cease all violation of this License, then your 404 | license from a particular copyright holder is reinstated (a) 405 | provisionally, unless and until the copyright holder explicitly and 406 | finally terminates your license, and (b) permanently, if the copyright 407 | holder fails to notify you of the violation by some reasonable means 408 | prior to 60 days after the cessation. 409 | 410 | Moreover, your license from a particular copyright holder is 411 | reinstated permanently if the copyright holder notifies you of the 412 | violation by some reasonable means, this is the first time you have 413 | received notice of violation of this License (for any work) from that 414 | copyright holder, and you cure the violation prior to 30 days after 415 | your receipt of the notice. 416 | 417 | Termination of your rights under this section does not terminate the 418 | licenses of parties who have received copies or rights from you under 419 | this License. If your rights have been terminated and not permanently 420 | reinstated, you do not qualify to receive new licenses for the same 421 | material under section 10. 422 | 423 | 9. Acceptance Not Required for Having Copies. 424 | 425 | You are not required to accept this License in order to receive or 426 | run a copy of the Program. Ancillary propagation of a covered work 427 | occurring solely as a consequence of using peer-to-peer transmission 428 | to receive a copy likewise does not require acceptance. However, 429 | nothing other than this License grants you permission to propagate or 430 | modify any covered work. These actions infringe copyright if you do 431 | not accept this License. Therefore, by modifying or propagating a 432 | covered work, you indicate your acceptance of this License to do so. 433 | 434 | 10. Automatic Licensing of Downstream Recipients. 435 | 436 | Each time you convey a covered work, the recipient automatically 437 | receives a license from the original licensors, to run, modify and 438 | propagate that work, subject to this License. You are not responsible 439 | for enforcing compliance by third parties with this License. 440 | 441 | An "entity transaction" is a transaction transferring control of an 442 | organization, or substantially all assets of one, or subdividing an 443 | organization, or merging organizations. If propagation of a covered 444 | work results from an entity transaction, each party to that 445 | transaction who receives a copy of the work also receives whatever 446 | licenses to the work the party's predecessor in interest had or could 447 | give under the previous paragraph, plus a right to possession of the 448 | Corresponding Source of the work from the predecessor in interest, if 449 | the predecessor has it or can get it with reasonable efforts. 450 | 451 | You may not impose any further restrictions on the exercise of the 452 | rights granted or affirmed under this License. For example, you may 453 | not impose a license fee, royalty, or other charge for exercise of 454 | rights granted under this License, and you may not initiate litigation 455 | (including a cross-claim or counterclaim in a lawsuit) alleging that 456 | any patent claim is infringed by making, using, selling, offering for 457 | sale, or importing the Program or any portion of it. 458 | 459 | 11. Patents. 460 | 461 | A "contributor" is a copyright holder who authorizes use under this 462 | License of the Program or a work on which the Program is based. The 463 | work thus licensed is called the contributor's "contributor version". 464 | 465 | A contributor's "essential patent claims" are all patent claims 466 | owned or controlled by the contributor, whether already acquired or 467 | hereafter acquired, that would be infringed by some manner, permitted 468 | by this License, of making, using, or selling its contributor version, 469 | but do not include claims that would be infringed only as a 470 | consequence of further modification of the contributor version. For 471 | purposes of this definition, "control" includes the right to grant 472 | patent sublicenses in a manner consistent with the requirements of 473 | this License. 474 | 475 | Each contributor grants you a non-exclusive, worldwide, royalty-free 476 | patent license under the contributor's essential patent claims, to 477 | make, use, sell, offer for sale, import and otherwise run, modify and 478 | propagate the contents of its contributor version. 479 | 480 | In the following three paragraphs, a "patent license" is any express 481 | agreement or commitment, however denominated, not to enforce a patent 482 | (such as an express permission to practice a patent or covenant not to 483 | sue for patent infringement). To "grant" such a patent license to a 484 | party means to make such an agreement or commitment not to enforce a 485 | patent against the party. 486 | 487 | If you convey a covered work, knowingly relying on a patent license, 488 | and the Corresponding Source of the work is not available for anyone 489 | to copy, free of charge and under the terms of this License, through a 490 | publicly available network server or other readily accessible means, 491 | then you must either (1) cause the Corresponding Source to be so 492 | available, or (2) arrange to deprive yourself of the benefit of the 493 | patent license for this particular work, or (3) arrange, in a manner 494 | consistent with the requirements of this License, to extend the patent 495 | license to downstream recipients. "Knowingly relying" means you have 496 | actual knowledge that, but for the patent license, your conveying the 497 | covered work in a country, or your recipient's use of the covered work 498 | in a country, would infringe one or more identifiable patents in that 499 | country that you have reason to believe are valid. 500 | 501 | If, pursuant to or in connection with a single transaction or 502 | arrangement, you convey, or propagate by procuring conveyance of, a 503 | covered work, and grant a patent license to some of the parties 504 | receiving the covered work authorizing them to use, propagate, modify 505 | or convey a specific copy of the covered work, then the patent license 506 | you grant is automatically extended to all recipients of the covered 507 | work and works based on it. 508 | 509 | A patent license is "discriminatory" if it does not include within 510 | the scope of its coverage, prohibits the exercise of, or is 511 | conditioned on the non-exercise of one or more of the rights that are 512 | specifically granted under this License. You may not convey a covered 513 | work if you are a party to an arrangement with a third party that is 514 | in the business of distributing software, under which you make payment 515 | to the third party based on the extent of your activity of conveying 516 | the work, and under which the third party grants, to any of the 517 | parties who would receive the covered work from you, a discriminatory 518 | patent license (a) in connection with copies of the covered work 519 | conveyed by you (or copies made from those copies), or (b) primarily 520 | for and in connection with specific products or compilations that 521 | contain the covered work, unless you entered into that arrangement, 522 | or that patent license was granted, prior to 28 March 2007. 523 | 524 | Nothing in this License shall be construed as excluding or limiting 525 | any implied license or other defenses to infringement that may 526 | otherwise be available to you under applicable patent law. 527 | 528 | 12. No Surrender of Others' Freedom. 529 | 530 | If conditions are imposed on you (whether by court order, agreement or 531 | otherwise) that contradict the conditions of this License, they do not 532 | excuse you from the conditions of this License. If you cannot convey a 533 | covered work so as to satisfy simultaneously your obligations under this 534 | License and any other pertinent obligations, then as a consequence you may 535 | not convey it at all. For example, if you agree to terms that obligate you 536 | to collect a royalty for further conveying from those to whom you convey 537 | the Program, the only way you could satisfy both those terms and this 538 | License would be to refrain entirely from conveying the Program. 539 | 540 | 13. Remote Network Interaction; Use with the GNU General Public License. 541 | 542 | Notwithstanding any other provision of this License, if you modify the 543 | Program, your modified version must prominently offer all users 544 | interacting with it remotely through a computer network (if your version 545 | supports such interaction) an opportunity to receive the Corresponding 546 | Source of your version by providing access to the Corresponding Source 547 | from a network server at no charge, through some standard or customary 548 | means of facilitating copying of software. This Corresponding Source 549 | shall include the Corresponding Source for any work covered by version 3 550 | of the GNU General Public License that is incorporated pursuant to the 551 | following paragraph. 552 | 553 | Notwithstanding any other provision of this License, you have 554 | permission to link or combine any covered work with a work licensed 555 | under version 3 of the GNU General Public License into a single 556 | combined work, and to convey the resulting work. The terms of this 557 | License will continue to apply to the part which is the covered work, 558 | but the work with which it is combined will remain governed by version 559 | 3 of the GNU General Public License. 560 | 561 | 14. Revised Versions of this License. 562 | 563 | The Free Software Foundation may publish revised and/or new versions of 564 | the GNU Affero General Public License from time to time. Such new versions 565 | will be similar in spirit to the present version, but may differ in detail to 566 | address new problems or concerns. 567 | 568 | Each version is given a distinguishing version number. If the 569 | Program specifies that a certain numbered version of the GNU Affero General 570 | Public License "or any later version" applies to it, you have the 571 | option of following the terms and conditions either of that numbered 572 | version or of any later version published by the Free Software 573 | Foundation. If the Program does not specify a version number of the 574 | GNU Affero General Public License, you may choose any version ever published 575 | by the Free Software Foundation. 576 | 577 | If the Program specifies that a proxy can decide which future 578 | versions of the GNU Affero General Public License can be used, that proxy's 579 | public statement of acceptance of a version permanently authorizes you 580 | to choose that version for the Program. 581 | 582 | Later license versions may give you additional or different 583 | permissions. However, no additional obligations are imposed on any 584 | author or copyright holder as a result of your choosing to follow a 585 | later version. 586 | 587 | 15. Disclaimer of Warranty. 588 | 589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 597 | 598 | 16. Limitation of Liability. 599 | 600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 608 | SUCH DAMAGES. 609 | 610 | 17. Interpretation of Sections 15 and 16. 611 | 612 | If the disclaimer of warranty and limitation of liability provided 613 | above cannot be given local legal effect according to their terms, 614 | reviewing courts shall apply local law that most closely approximates 615 | an absolute waiver of all civil liability in connection with the 616 | Program, unless a warranty or assumption of liability accompanies a 617 | copy of the Program in return for a fee. 618 | 619 | END OF TERMS AND CONDITIONS -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simple Logger 2 | This package is a simple yet complete logging management system for python based applications. 3 | * Logging to two streams is allowed, by default the first one is the standard output (terminal). 4 | * Logging text formatting (text color, text weight, background color) is allowed. 5 | * Adding as many logging levels and types as needed is possible. 6 | 7 | ## Installation 8 | pip install pysimplelog 9 | 10 | ## Online Documentation 11 | http://bachiraoun.github.io/pysimplelog/ 12 | 13 | ## Author 14 | Bachir Aoun 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /SimpleLog.py: -------------------------------------------------------------------------------- 1 | """ 2 | Usage 3 | ===== 4 | .. code-block:: python 5 | 6 | # import python 2.7.x 3.x.y compatible print function 7 | from __future__ import print_function 8 | # import Logger 9 | from pysimplelog import Logger 10 | 11 | # initialize 12 | l=Logger("log test") 13 | 14 | # change log file basename from simplelog to mylog 15 | l.set_log_file_basename("mylog") 16 | 17 | # change log file extension from .log to .pylog 18 | l.set_log_file_extension("pylog") 19 | 20 | # Add new log types. 21 | l.add_log_type("super critical", name="SUPER CRITICAL", level=200, color='red', attributes=["bold","underline"]) 22 | l.add_log_type("wrong", name="info", color='magenta', attributes=["strike through"]) 23 | l.add_log_type("important", name="info", color='black', highlight="orange", attributes=["bold"]) 24 | 25 | # update error log type 26 | l.update_log_type(logType='error', color='pink', attributes=['underline','bold']) 27 | 28 | # print logger 29 | print(l, end="\\n\\n") 30 | 31 | # test logging 32 | l.info("I am info, called using my shortcut method.") 33 | l.log("info", "I am info, called using log method.") 34 | 35 | l.warn("I am warn, called using my shortcut method.") 36 | l.log("warn", "I am warn, called using log method.") 37 | 38 | l.error("I am error, called using my shortcut method.") 39 | l.log("error", "I am error, called using log method.") 40 | 41 | l.critical("I am critical, called using my shortcut method.") 42 | l.log("critical", "I am critical, called using log method.") 43 | 44 | l.debug("I am debug, called using my shortcut method.") 45 | l.log("debug", "I am debug, called using log method.") 46 | 47 | l.log("super critical", "I am super critical, called using log method because I have no shortcut method.") 48 | l.log("wrong", "I am wrong, called using log method because I have no shortcut method.") 49 | l.log("important", "I am important, called using log method because I have no shortcut method.") 50 | 51 | # print last logged messages 52 | print("") 53 | print("Last logged messages are:") 54 | print("=========================") 55 | print(l.lastLoggedMessage) 56 | print(l.lastLoggedDebug) 57 | print(l.lastLoggedInfo) 58 | print(l.lastLoggedWarning) 59 | print(l.lastLoggedError) 60 | print(l.lastLoggedCritical) 61 | 62 | # log data 63 | print("") 64 | print("Log random data and traceback stack:") 65 | print("====================================") 66 | l.info("Check out this data", data=list(range(10))) 67 | print("") 68 | 69 | # log error with traceback 70 | import traceback 71 | try: 72 | 1/range(10) 73 | except Exception as err: 74 | l.error('%s (is this python ?)'%err, tback=traceback.extract_stack()) 75 | 76 | 77 | output 78 | ====== 79 | .. raw:: html 80 | 81 | 82 |
  83 | 
  84 |             Logger (Version %AUTO_VERSION)
  85 |             log type       |log name       |level     |std flag  |file flag |
  86 |             ---------------|---------------|----------|----------|----------|
  87 |             wrong          |info           |0.0       |True      |True      |
  88 |             debug          |DEBUG          |0.0       |True      |True      |
  89 |             important      |info           |0.0       |True      |True      |
  90 |             info           |INFO           |10.0      |True      |True      |
  91 |             warn           |WARNING        |20.0      |True      |True      |
  92 |             error          |ERROR          |30.0      |True      |True      |
  93 |             critical       |CRITICAL       |100.0     |True      |True      |
  94 |             super critical |SUPER CRITICAL |200.0     |True      |True      |
  95 | 
  96 |             2018-09-07 16:07:58 - log test <INFO> I am info, called using my shortcut method.
  97 |             2018-09-07 16:07:58 - log test <INFO> I am  info, called using log method.
  98 |             2018-09-07 16:07:58 - log test <WARNING> I am warn, called using my shortcut method.
  99 |             2018-09-07 16:07:58 - log test <WARNING> I am warn, called using log method.
 100 |             2018-09-07 16:07:58 - log test <ERROR> I am error, called using my shortcut method.
 101 |             2018-09-07 16:07:58 - log test <ERROR> I am error, called using log method.
 102 |             2018-09-07 16:07:58 - log test <CRITICAL> I am critical, called using my shortcut method.
 103 |             2018-09-07 16:07:58 - log test <CRITICAL> I critical, called using log method.
 104 |             2018-09-07 16:07:58 - log test <DEBUG> I am debug, called using my shortcut method.
 105 |             2018-09-07 16:07:58 - log test <DEBUG> I am debug, called using log method.
 106 |             2018-09-07 16:07:58 - log test <SUPER CRITICAL> I am super critical, called using log method because I have no shortcut method.
 107 |             2018-09-07 16:07:58 - log test <info> I am wrong, called using log method because I have no shortcut method.
 108 |             2015-11-18 14:25:08 - log test <info> I am important, called using log method because I have no shortcut method.
 109 | 
 110 |             Last logged messages are:
 111 |             =========================
 112 |             I am important, called using log method because I have no shortcut method.
 113 |             I am debug, called using log method.
 114 |             I am  info, called using log method.
 115 |             I am warn, called using log method.
 116 |             I am error, called using log method.
 117 |             I am critical, called using log method.
 118 | 
 119 |             Log random data and traceback stack:
 120 |             ====================================
 121 |             2018-09-07 16:07:58 - log test  Check out this data
 122 |             [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
 123 |             2015-11-18 14:25:08 - log test <ERROR> unsupported operand type(s) for /: 'int' and 'list' (is this python ?)
 124 |               File "<stdin>", line 4, in <module>
 125 | 
 126 | 
 127 |         
128 | 129 | 130 | """ 131 | # python standard distribution imports 132 | import os, sys, copy, re, atexit 133 | from datetime import datetime 134 | 135 | # python version dependant imports 136 | if sys.version_info >= (3, 0): 137 | # This is python 3 138 | str = str 139 | long = int 140 | unicode = str 141 | bytes = bytes 142 | basestring = str 143 | else: 144 | str = str 145 | unicode = unicode 146 | bytes = str 147 | long = long 148 | basestring = basestring 149 | 150 | # import pysimplelog version 151 | try: 152 | from __pkginfo__ import __version__ 153 | except: 154 | from .__pkginfo__ import __version__ 155 | 156 | 157 | # useful definitions 158 | def _is_number(number): 159 | if isinstance(number, (int, long, float, complex)): 160 | return True 161 | try: 162 | float(number) 163 | except: 164 | return False 165 | else: 166 | return True 167 | 168 | def _normalize_path(path): 169 | if os.sep=='\\': 170 | path = re.sub(r'([\\])\1+', r'\1', path).replace('\\','\\\\') 171 | return path 172 | 173 | 174 | class Logger(object): 175 | """ 176 | This is simplelog main Logger class definition.\n 177 | 178 | A logging is constituted of a header a message and a footer. 179 | In the current implementation the footer is empty and the header is as the following:\n 180 | date time - loggerName \n 181 | 182 | In order to change any of the header or the footer, '_get_header' and '_get_footer' 183 | methods must be overloaded. 184 | 185 | When used in a python application, it is advisable to use Logger singleton 186 | implementation and not Logger itself. 187 | if no overloading is needed one can simply import the singleton as the following: 188 | 189 | .. code-block:: python 190 | 191 | from pysimplelog import SingleLogger as Logger 192 | 193 | 194 | A new Logger instanciates with the following logType list (logTypes : level) 195 | 196 | * debug : 0 197 | * info : 10 198 | * warn : 20 199 | * error : 30 200 | * critical : 100 201 | 202 | 203 | Recommended overloading implementation, this is how it could be done: 204 | 205 | .. code-block:: python 206 | 207 | from pysimplelog import SingleLogger as LOG 208 | 209 | class Logger(LOG): 210 | # *args and **kwargs can be replace by fixed arguments 211 | def custom_init(self, *args, **kwargs): 212 | # hereinafter any further instanciation can be coded 213 | 214 | 215 | 216 | In case overloading __init__ is needed, this is how it could be done: 217 | 218 | .. code-block:: python 219 | 220 | from pysimplelog import SingleLogger as LOG 221 | 222 | class Logger(LOG): 223 | # custom_init will still be called in super(Logger, self).__init__(*args, **kwargs) 224 | def __init__(self, *args, **kwargs): 225 | if self._isInitialized: return 226 | super(Logger, self).__init__(*args, **kwargs) 227 | # hereinafter any further instanciation can be coded 228 | 229 | 230 | :Parameters: 231 | #. name (string): The logger name. 232 | #. flush (boolean): Whether to always flush the logging streams. 233 | #. logToStdout (boolean): Whether to log to the standard output stream. 234 | #. stdout (None, stream): The standard output stream. If None, system 235 | standard output will be set automatically. Otherwise any stream with 236 | read and write methods can be passed 237 | #. logToFile (boolean): Whether to log to to file. 238 | #. logFile (None, string): the full log file path including directory 239 | basename and extension. If this is given, all of logFileBasename and 240 | logFileExtension will be discarded. logfile is equivalent to 241 | logFileBasename.logFileExtension 242 | #. logFileBasename (string): Logging file directory path and file 243 | basename. A logging file full name is set as 244 | logFileBasename.logFileExtension 245 | #. logFileExtension (string): Logging file extension. A logging file 246 | full name is set as logFileBasename.logFileExtension 247 | #. logFileMaxSize (None, number): The maximum size in Megabytes 248 | of a logging file. Once exceeded, another logging file as 249 | logFileBasename_N.logFileExtension will be created. 250 | Where N is an automatically incremented number. If None or a 251 | negative number is given, the logging file will grow 252 | indefinitely 253 | #. logFileFirstNumber (None, integer): first log file number 'N' in 254 | logFileBasename_N.logFileExtension. If None is given then 255 | first log file will be logFileBasename.logFileExtension and ince 256 | logFileMaxSize is reached second log file will be 257 | logFileBasename_0.logFileExtension and so on and so forth. 258 | If number is given it must be an integer >=0 259 | #. logFileRoll (None, intger): If given, it sets the maximum number of 260 | log files to write. Exceeding the number will result in deleting 261 | previous ones. This also insures always increasing files numbering. 262 | #. stdoutMinLevel(None, number): The minimum logging to system standard 263 | output level. If None, standard output minimum level checking is left 264 | out. 265 | #. stdoutMaxLevel(None, number): The maximum logging to system standard 266 | output level. If None, standard output maximum level checking is 267 | left out. 268 | #. fileMinLevel(None, number): The minimum logging to file level. 269 | If None, file minimum level checking is left out. 270 | #. fileMaxLevel(None, number): The maximum logging to file level. 271 | If None, file maximum level checking is left out. 272 | #. logTypes (None, dict): Used to create and update existing log types 273 | upon initialization. Given dictionary keys are logType 274 | (new or existing) and values can be None or a dictionary of kwargs 275 | to call update_log_type upon. This argument will be called after 276 | custom_init 277 | #. timezone (None, str): Logging time timezone. If provided 278 | pytz must be installed and it must be the timezone name. If not 279 | provided, the machine default timezone will be used. 280 | #. \*args: This is used to send non-keyworded variable length argument 281 | list to custom initialize. args will be parsed and used in 282 | custom_init method. 283 | #. \**kwargs: This allows passing keyworded variable length of 284 | arguments to custom_init method. kwargs can be anything other 285 | than __init__ arguments. 286 | """ 287 | def __init__(self, name="logger", flush=True, 288 | logToStdout=True, stdout=None, 289 | logToFile=True, logFile=None, 290 | logFileBasename="simplelog", logFileExtension="log", 291 | logFileMaxSize=10, logFileFirstNumber=0, logFileRoll=None, 292 | stdoutMinLevel=None, stdoutMaxLevel=None, 293 | fileMinLevel=None, fileMaxLevel=None, 294 | logTypes=None, timezone=None,*args, **kwargs): 295 | # set last logged message 296 | self.__lastLogged = {} 297 | # instanciate file stream 298 | self.__logFileStream = None 299 | # set timezone 300 | self.set_timezone(timezone) 301 | # set name 302 | self.set_name(name) 303 | # set flush 304 | self.set_flush(flush) 305 | # set log to stdout 306 | self.set_log_to_stdout_flag(logToStdout) 307 | # set stdout 308 | self.set_stdout(stdout) 309 | # set log file roll 310 | self.set_log_file_roll(logFileRoll) 311 | # set log to file 312 | self.set_log_to_file_flag(logToFile) 313 | # set maximum logFile size 314 | self.set_log_file_maximum_size(logFileMaxSize) 315 | # set logFile first number 316 | self.set_log_file_first_number(logFileFirstNumber) 317 | # set logFile basename and extension 318 | if logFile is not None: 319 | self.set_log_file(logFile) 320 | else: 321 | self.__set_log_file_basename(logFileBasename) 322 | self.set_log_file_extension(logFileExtension) 323 | # initialize types parameters 324 | self.__logTypeFileFlags = {} 325 | self.__logTypeStdoutFlags = {} 326 | self.__logTypeNames = {} 327 | self.__logTypeLevels = {} 328 | self.__logTypeFormat = {} 329 | self.__logTypeColor = {} 330 | self.__logTypeHighlight = {} 331 | self.__logTypeAttributes = {} 332 | # initialize forced levels 333 | self.__forcedStdoutLevels = {} 334 | self.__forcedFileLevels = {} 335 | # set levels 336 | self.__stdoutMinLevel = None 337 | self.__stdoutMaxLevel = None 338 | self.__fileMinLevel = None 339 | self.__fileMaxLevel = None 340 | # create log messages counter 341 | self.__logMessagesCounter = {} 342 | self.set_minimum_level(stdoutMinLevel, stdoutFlag=True, fileFlag=False) 343 | self.set_maximum_level(stdoutMaxLevel, stdoutFlag=True, fileFlag=False) 344 | self.set_minimum_level(fileMinLevel, stdoutFlag=False, fileFlag=True) 345 | self.set_maximum_level(fileMaxLevel, stdoutFlag=False, fileFlag=True) 346 | # create default types 347 | self.add_log_type("debug", name="DEBUG", level=0, stdoutFlag=None, fileFlag=None, color=None, highlight=None, attributes=None) 348 | self.add_log_type("info", name="INFO", level=10, stdoutFlag=None, fileFlag=None, color=None, highlight=None, attributes=None) 349 | self.add_log_type("warn", name="WARNING", level=20, stdoutFlag=None, fileFlag=None, color=None, highlight=None, attributes=None) 350 | self.add_log_type("error", name="ERROR", level=30, stdoutFlag=None, fileFlag=None, color=None, highlight=None, attributes=None) 351 | self.add_log_type("critical", name="CRITICAL", level=100, stdoutFlag=None, fileFlag=None, color=None, highlight=None, attributes=None) 352 | # custom initialize 353 | self.custom_init( *args, **kwargs ) 354 | # add logTypes 355 | if logTypes is not None: 356 | assert isinstance(logTypes, dict),"logTypes must be None or a dictionary" 357 | assert all([isinstance(lt, basestring) for lt in logTypes]), "logTypes dictionary keys must be strings" 358 | assert all([isinstance(logTypes[lt], dict) for lt in logTypes if logTypes[lt] is not None]), "logTypes dictionary values must be all None or dictionaries" 359 | for lt in logTypes: 360 | ltv = logTypes[lt] 361 | if ltv is None: 362 | ltv = {} 363 | if not self.is_logType(lt): 364 | self.add_log_type(lt, **ltv) 365 | elif len(ltv): 366 | self.update_log_type(lt, **ltv) 367 | # flush at python exit 368 | atexit.register(self._flush_atexit_logfile) 369 | 370 | def __str__(self): 371 | # create version 372 | string = self.__class__.__name__+" (Version "+str(__version__)+")" 373 | # add general properties 374 | #string += "\n - Log To Standard Output General Flag: %s"%(self.__logToStdout) 375 | #string += "\n - Log To Standard Output Minimum Level: %s"%(self.__stdoutMinLevel) 376 | #string += "\n - Log To Standard Output Maximum Level: %s"%(self.__stdoutMaxLevel) 377 | #string += "\n - Log To File General Flag: %s"%(self.__logToFile) 378 | #string += "\n - Log To File Minimum Level: %s"%(self.__fileMinLevel) 379 | #string += "\n - Log To File Maximum Level: %s"%(self.__fileMaxLevel) 380 | string += "\n - Log To Stdout: Flag (%s) - Min Level (%s) - Max Level (%s)"%(self.__logToStdout,self.__stdoutMinLevel,self.__stdoutMaxLevel) 381 | string += "\n - Log To File: Flag (%s) - Min Level (%s) - Max Level (%s)"%(self.__logToFile,self.__fileMinLevel,self.__fileMaxLevel) 382 | string += "\n File Size (%s) - First Number (%s) - Roll (%s)"%(self.__logFileMaxSize,self.__logFileFirstNumber,self.__logFileRoll) 383 | string += "\n Current log file (%s)"%(self.__logFileName) 384 | # add log types table 385 | if not len(self.__logTypeNames): 386 | string += "\nlog type |log name |level |std flag |file flag" 387 | string += "\n----------|----------|----------|-----------|---------" 388 | return string 389 | # get maximum logType and logTypeZName and maxLogLevel 390 | maxLogType = max( max([len(k)+1 for k in self.__logTypeNames]), len("log type ")) 391 | maxLogName = max( max([len(self.__logTypeNames[k])+1 for k in self.__logTypeNames]), len("log name ")) 392 | maxLogLevel = max( max([len(str(self.__logTypeLevels[k]))+1 for k in self.__logTypeLevels]), len("level ")) 393 | # create header 394 | string += "\n"+ "log type".ljust(maxLogType) + "|" +\ 395 | "log name".ljust(maxLogName) + "|" +\ 396 | "level".ljust(maxLogLevel) + "|" +\ 397 | "std flag".ljust(10) + "|" +\ 398 | "file flag".ljust(10) + "|" 399 | # create separator 400 | string += "\n"+ "-"*maxLogType + "|" +\ 401 | "-"*maxLogName + "|" +\ 402 | "-"*maxLogLevel + "|" +\ 403 | "-"*10 + "|" +\ 404 | "-"*10 + "|" 405 | # order from min level to max level 406 | keys = sorted(self.__logTypeLevels, key=self.__logTypeLevels.__getitem__) 407 | # append log types 408 | for k in keys: 409 | string += "\n"+ str(k).ljust(maxLogType) + "|" +\ 410 | str(self.__logTypeNames[k]).ljust(maxLogName) + "|" +\ 411 | str(self.__logTypeLevels[k]).ljust(maxLogLevel) + "|" +\ 412 | str(self.__logTypeStdoutFlags[k]).ljust(10) + "|" +\ 413 | str(self.logTypeFileFlags[k]).ljust(10) + "|" 414 | return string 415 | 416 | def __stream_format_allowed(self, stream): 417 | """ 418 | Check whether a stream allows formatting such as coloring. 419 | Inspired from Python cookbook, #475186 420 | """ 421 | # curses isn't available on all platforms 422 | try: 423 | import curses as CURSES 424 | except: 425 | return False 426 | try: 427 | CURSES.setupterm() 428 | return CURSES.tigetnum("colors") >= 2 429 | except: 430 | return False 431 | 432 | def __get_stream_fonts_attributes(self, stream): 433 | # foreground color 434 | fgNames = ["black","red","green","orange","blue","magenta","cyan","grey"] 435 | fgCode = [str(idx) for idx in range(30,38,1)] 436 | fgNames.extend(["dark grey","light red","light green","yellow","light blue","pink","light cyan"]) 437 | fgCode.extend([str(idx) for idx in range(90,97,1)]) 438 | # background color 439 | bgNames = ["black","red","green","orange","blue","magenta","cyan","grey"] 440 | bgCode = [str(idx) for idx in range(40,48,1)] 441 | # attributes 442 | attrNames = ["bold","underline","blink","invisible","strike through"] 443 | attrCode = ["1","4","5","8","9"] 444 | # set reset 445 | resetCode = "0" 446 | # if attributing is not allowed 447 | if not self.__stream_format_allowed(stream): 448 | fgCode = ["" for idx in fgCode] 449 | bgCode = ["" for idx in bgCode] 450 | attrCode = ["" for idx in attrCode] 451 | resetCode = "" 452 | # set font attributes dict 453 | color = dict( [(fgNames[idx],fgCode[idx]) for idx in range(len(fgCode))] ) 454 | highlight = dict( [(bgNames[idx],bgCode[idx]) for idx in range(len(bgCode))] ) 455 | attributes = dict( [(attrNames[idx],attrCode[idx]) for idx in range(len(attrCode))] ) 456 | return {"color":color, "highlight":highlight, "attributes":attributes, "reset":resetCode} 457 | 458 | def _flush_atexit_logfile(self): 459 | if self.__logFileStream is not None: 460 | try: 461 | self.__logFileStream.flush() 462 | except: 463 | pass 464 | try: 465 | os.fsync(self.__logFileStream.fileno()) 466 | except: 467 | pass 468 | self.__logFileStream.close() 469 | 470 | @property 471 | def lastLogged(self): 472 | """Get a dictionary of last logged messages. 473 | Keys are log types and values are the the last messages.""" 474 | d = copy.deepcopy(self.__lastLogged) 475 | d.pop(-1, None) 476 | return d 477 | 478 | @property 479 | def lastLoggedMessage(self): 480 | """Get last logged message of any type. Retuns None if no message was logged.""" 481 | return self.__lastLogged.get(-1, None) 482 | 483 | @property 484 | def lastLoggedDebug(self): 485 | """Get last logged message of type 'debug'. Retuns None if no message was logged.""" 486 | return self.__lastLogged.get('debug', None) 487 | 488 | @property 489 | def lastLoggedInfo(self): 490 | """Get last logged message of type 'info'. Retuns None if no message was logged.""" 491 | return self.__lastLogged.get('info', None) 492 | 493 | @property 494 | def lastLoggedWarning(self): 495 | """Get last logged message of type 'warn'. Retuns None if no message was logged.""" 496 | return self.__lastLogged.get('warn', None) 497 | 498 | @property 499 | def lastLoggedError(self): 500 | """Get last logged message of type 'error'. Retuns None if no message was logged.""" 501 | return self.__lastLogged.get('error', None) 502 | 503 | @property 504 | def lastLoggedCritical(self): 505 | """Get last logged message of type 'critical'. Retuns None if no message was logged.""" 506 | return self.__lastLogged.get('critical', None) 507 | 508 | @property 509 | def flush(self): 510 | """Flush flag.""" 511 | return self.__flush 512 | 513 | @property 514 | def logTypes(self): 515 | """list of all defined log types.""" 516 | return list(self.__logTypeNames) 517 | 518 | @property 519 | def logLevels(self): 520 | """dictionary copy of all defined log types levels.""" 521 | return copy.deepcopy(self.__logTypeLevels) 522 | 523 | @property 524 | def logTypeFileFlags(self): 525 | """dictionary copy of all defined log types logging to a file flags.""" 526 | return copy.deepcopy(self.__logTypeFileFlags) 527 | 528 | @property 529 | def logTypeStdoutFlags(self): 530 | """dictionary copy of all defined log types logging to Standard output flags.""" 531 | return copy.deepcopy(self.__logTypeStdoutFlags) 532 | 533 | @property 534 | def stdoutMinLevel(self): 535 | """Standard output minimum logging level.""" 536 | return self.__stdoutMinLevel 537 | 538 | @property 539 | def stdoutMaxLevel(self): 540 | """Standard output maximum logging level.""" 541 | return self.__stdoutMaxLevel 542 | 543 | @property 544 | def fileMinLevel(self): 545 | """file logging minimum level.""" 546 | return self.__fileMinLevel 547 | 548 | @property 549 | def fileMaxLevel(self): 550 | """file logging maximum level.""" 551 | return self.__fileMaxLevel 552 | 553 | @property 554 | def forcedStdoutLevels(self): 555 | """dictionary copy of forced flags of logging to standard output.""" 556 | return copy.deepcopy(self.__forcedStdoutLevels) 557 | 558 | @property 559 | def forcedFileLevels(self): 560 | """dictionary copy of forced flags of logging to file.""" 561 | return copy.deepcopy(self.__forcedFileLevels) 562 | 563 | @property 564 | def logTypeNames(self): 565 | """dictionary copy of all defined log types logging names.""" 566 | return copy.deepcopy(self.__logTypeNames) 567 | 568 | @property 569 | def logTypeLevels(self): 570 | """dictionary copy of all defined log types levels showing when logging.""" 571 | return copy.deepcopy(self.__logTypeLevels) 572 | 573 | @property 574 | def logTypeFormats(self): 575 | """dictionary copy of all defined log types format showing when logging.""" 576 | return copy.deepcopy(self.__logTypeFormats) 577 | 578 | @property 579 | def name(self): 580 | """logger name.""" 581 | return self.__name 582 | 583 | @property 584 | def logToStdout(self): 585 | """log to stdout flag.""" 586 | return self.__logToStdout 587 | 588 | @property 589 | def logFileRoll(self): 590 | """Log file roll parameter.""" 591 | return self.__logFileRoll 592 | 593 | @property 594 | def logToFile(self): 595 | """log to file flag.""" 596 | return self.__logToFile 597 | 598 | @property 599 | def logFileName(self): 600 | """currently used log file name.""" 601 | return self.__logFileName 602 | 603 | @property 604 | def logFileBasename(self): 605 | """log file basename.""" 606 | return self.__logFileBasename 607 | 608 | @property 609 | def logFileExtension(self): 610 | """log file extension.""" 611 | return self.__logFileExtension 612 | 613 | @property 614 | def logFileMaxSize(self): 615 | """maximum allowed logfile size in megabytes.""" 616 | return self.__logFileMaxSize 617 | 618 | @property 619 | def logFileFirstNumber(self): 620 | """log file first number""" 621 | return self.__logFileFirstNumber 622 | 623 | @property 624 | def timezone(self): 625 | """The timezone if given""" 626 | timezone = self.__timezone 627 | if timezone is not None: 628 | timezone = timezone.zone 629 | return timezone 630 | 631 | @property 632 | def _timezone(self): 633 | return self.__timezone 634 | 635 | @property 636 | def logMessagesCounter(self): 637 | """Counter look up table for all logged messages that were 638 | count constrainted""" 639 | return self.__logMessagesCounter 640 | 641 | def set_timezone(self, timezone): 642 | """ 643 | Set logging timezone 644 | 645 | :Parameters: 646 | #. timezone (None, str): Logging time timezone. If provided 647 | pytz must be installed and it must be the timezone name. If not 648 | provided, the machine default timezone will be used 649 | """ 650 | if timezone is not None: 651 | assert isinstance(timezone, basestring), "timezone must be None or a string" 652 | import pytz 653 | timezone = pytz.timezone(timezone) 654 | self.__timezone = timezone 655 | 656 | def is_logType(self, logType): 657 | """ 658 | Get whether given logType is defined or not 659 | 660 | :Parameters: 661 | #. logType (string): A defined logging type. 662 | 663 | :Result: 664 | #. result (boolean): Whether given logType is defined or not 665 | """ 666 | try: 667 | result = logType in self.__logTypeNames 668 | except: 669 | result = False 670 | finally: 671 | return result 672 | 673 | def update(self, **kwargs): 674 | """Update logger general parameters using key value pairs. 675 | Updatable parameters are name, flush, stdout, logToStdout, logFileRoll, 676 | logToFile, logFileMaxSize, stdoutMinLevel, stdoutMaxLevel, fileMinLevel, 677 | fileMaxLevel and logFileFirstNumber. 678 | """ 679 | # update name 680 | if "name" in kwargs: 681 | self.set_name(kwargs["name"]) 682 | # update flush 683 | if "flush" in kwargs: 684 | self.set_flush(kwargs["flush"]) 685 | # update stdout 686 | if "stdout" in kwargs: 687 | self.set_stdout(kwargs["stdout"]) 688 | # update logToStdout 689 | if "logToStdout" in kwargs: 690 | self.set_log_to_stdout_flag(kwargs["logToStdout"]) 691 | # update logFileRoll 692 | if "logFileRoll" in kwargs: 693 | self.set_log_file_roll(kwargs["logFileRoll"]) 694 | # update logToFile 695 | if "logToFile" in kwargs: 696 | self.set_log_to_file_flag(kwargs["logToFile"]) 697 | # update logFileMaxSize 698 | if "logFileMaxSize" in kwargs: 699 | self.set_log_file_maximum_size(kwargs["logFileMaxSize"]) 700 | # update logFileFirstNumber 701 | if "logFileFirstNumber" in kwargs: 702 | self.set_log_file_first_number(kwargs["logFileFirstNumber"]) 703 | # update stdoutMinLevel 704 | if "stdoutMinLevel" in kwargs: 705 | self.set_minimum_level(kwargs["stdoutMinLevel"], stdoutFlag=True, fileFlag=False) 706 | # update stdoutMaxLevel 707 | if "stdoutMaxLevel" in kwargs: 708 | self.set_maximum_level(kwargs["stdoutMaxLevel"], stdoutFlag=True, fileFlag=False) 709 | # update fileMinLevel 710 | if "fileMinLevel" in kwargs: 711 | self.set_minimum_level(kwargs["fileMinLevel"], stdoutFlag=False, fileFlag=True) 712 | # update fileMaxLevel 713 | if "fileMaxLevel" in kwargs: 714 | self.set_maximum_level(kwargs["fileMaxLevel"], stdoutFlag=False, fileFlag=True) 715 | # update logfile 716 | if "logFile" in kwargs: 717 | self.set_log_file(kwargs["logFile"]) 718 | 719 | 720 | @property 721 | def parameters(self): 722 | """get a dictionary of logger general parameters. The same dictionary 723 | can be used to update another logger instance using update method""" 724 | return {"name":self.__name, 725 | "flush":self.__flush, 726 | "stdout":None if self.__stdout is sys.stdout else self.__stdout, 727 | "logToStdout":self.__logToStdout, 728 | "logFileRoll":self.__logFileRoll, 729 | "logToFile":self.__logToFile, 730 | "logFileMaxSize":self.__logFileMaxSize, 731 | "logFileFirstNumber":self.__logFileFirstNumber, 732 | "stdoutMinLevel":self.__stdoutMinLevel, 733 | "stdoutMaxLevel":self.__stdoutMaxLevel, 734 | "fileMinLevel":self.__fileMinLevel, 735 | "fileMaxLevel":self.__fileMaxLevel, 736 | "logFile":self.__logFileBasename+"."+self.__logFileExtension} 737 | 738 | 739 | def custom_init(self, *args, **kwargs): 740 | """ 741 | Custom initialize abstract method. This method will be called at the end of 742 | initialzation. This method needs to be overloaded to custom initialize 743 | Logger instances. 744 | 745 | :Parameters: 746 | #. \*args (): This is used to send non-keyworded variable length argument 747 | list to custom initialize. 748 | #. \**kwargs (): This is keyworded variable length of arguments. 749 | kwargs can be anything other than __init__ arguments. 750 | """ 751 | pass 752 | 753 | def set_name(self, name): 754 | """ 755 | Set the logger name. 756 | 757 | :Parameters: 758 | #. name (string): The logger name. 759 | """ 760 | assert isinstance(name, basestring), "name must be a string" 761 | self.__name = name 762 | 763 | def set_flush(self, flush): 764 | """ 765 | Set the logger flush flag. 766 | 767 | :Parameters: 768 | #. flush (boolean): Whether to always flush the logging streams. 769 | """ 770 | assert isinstance(flush, bool), "flush must be boolean" 771 | self.__flush = flush 772 | 773 | def set_stdout(self, stream=None): 774 | """ 775 | Set the logger standard output stream. 776 | 777 | :Parameters: 778 | #. stdout (None, stream): The standard output stream. If None, system standard 779 | output will be set automatically. Otherwise any stream with read and write 780 | methods can be passed 781 | """ 782 | if stream is None: 783 | self.__stdout = sys.stdout 784 | else: 785 | assert hasattr(stream, 'read') and hasattr(stream, 'write'), "stdout stream is not valid" 786 | self.__stdout = stream 787 | # set stdout colors 788 | self.__stdoutFontFormat = self.__get_stream_fonts_attributes(stream) 789 | 790 | def set_log_to_stdout_flag(self, logToStdout): 791 | """ 792 | Set the logging to the defined standard output flag. When set to False, 793 | no logging to standard output will happen regardless of a logType 794 | standard output flag. 795 | 796 | :Parameters: 797 | #. logToStdout (boolean): Whether to log to the standard output stream. 798 | """ 799 | assert isinstance(logToStdout, bool), "logToStdout must be boolean" 800 | self.__logToStdout = logToStdout 801 | 802 | def set_log_to_file_flag(self, logToFile): 803 | """ 804 | Set the logging to a file general flag. When set to False, no logging 805 | to file will happen regardless of a logType file flag. 806 | 807 | :Parameters: 808 | #. logToFile (boolean): Whether to log to to file. 809 | """ 810 | assert isinstance(logToFile, bool), "logToFile must be boolean" 811 | self.__logToFile = logToFile 812 | 813 | def set_log_type_flags(self, logType, stdoutFlag, fileFlag): 814 | """ 815 | Set a defined log type flags. 816 | 817 | :Parameters: 818 | #. logType (string): A defined logging type. 819 | #. stdoutFlag (boolean): Whether to log to the standard output stream. 820 | #. fileFlag (boolean): Whether to log to to file. 821 | """ 822 | assert logType in self.__logTypeStdoutFlags, "logType '%s' not defined" %logType 823 | assert isinstance(stdoutFlag, bool), "stdoutFlag must be boolean" 824 | assert isinstance(fileFlag, bool), "fileFlag must be boolean" 825 | self.__logTypeStdoutFlags[logType] = stdoutFlag 826 | self.__logTypeFileFlags[logType] = fileFlag 827 | 828 | def set_log_file_roll(self, logFileRoll): 829 | """ 830 | Set roll parameter to determine the maximum number of log files allowed. 831 | Beyond the maximum, older will be removed. 832 | 833 | :Parameters: 834 | #. logFileRoll (None, intger): If given, it sets the maximum number of 835 | log files to write. Exceeding the number will result in deleting 836 | older files. This also insures always increasing files numbering. 837 | Log files will be identified in increasing N order of 838 | logFileBasename_N.logFileExtension pattern. Be careful setting 839 | this parameter as old log files will be permanently deleted if 840 | the number of files exceeds the value of logFileRoll 841 | """ 842 | if logFileRoll is not None: 843 | assert isinstance(logFileRoll, int), "logFileRoll must be None or integer" 844 | assert logFileRoll>0, "integer logFileRoll must be >0" 845 | self.__logFileRoll = logFileRoll 846 | 847 | def set_log_file(self, logfile): 848 | """ 849 | Set the log file full path including directory path basename and extension. 850 | 851 | :Parameters: 852 | #. logFile (string): the full log file path including basename and 853 | extension. If this is given, all of logFileBasename and logFileExtension 854 | will be discarded. logfile is equivalent to logFileBasename.logFileExtension 855 | """ 856 | assert isinstance(logfile, basestring), "logfile must be a string" 857 | basename, extension = os.path.splitext(logfile) 858 | self.__set_log_file_basename(logFileBasename=basename) 859 | self.set_log_file_extension(logFileExtension=extension) 860 | 861 | def set_log_file_extension(self, logFileExtension): 862 | """ 863 | Set the log file extension. 864 | 865 | :Parameters: 866 | #. logFileExtension (string): Logging file extension. A logging file full name is 867 | set as logFileBasename.logFileExtension 868 | """ 869 | assert isinstance(logFileExtension, basestring), "logFileExtension must be a basestring" 870 | assert len(logFileExtension), "logFileExtension can't be empty" 871 | if logFileExtension[0] == ".": 872 | logFileExtension = logFileExtension[1:] 873 | assert len(logFileExtension), "logFileExtension is not allowed to be single dot" 874 | if logFileExtension[-1] == ".": 875 | logFileExtension = logFileExtension[:-1] 876 | assert len(logFileExtension), "logFileExtension is not allowed to be double dots" 877 | self.__logFileExtension = logFileExtension 878 | # set log file name 879 | self.__set_log_file_name() 880 | 881 | def set_log_file_basename(self, logFileBasename): 882 | """ 883 | Set the log file basename. 884 | 885 | :Parameters: 886 | #. logFileBasename (string): Logging file directory path and file basename. 887 | A logging file full name is set as logFileBasename.logFileExtension 888 | """ 889 | self.__set_log_file_basename(logFileBasename) 890 | # set log file name 891 | self.__set_log_file_name() 892 | 893 | def __set_log_file_basename(self, logFileBasename): 894 | assert isinstance(logFileBasename, basestring), "logFileBasename must be a basestring" 895 | self.__logFileBasename = _normalize_path(logFileBasename)#logFileBasename 896 | 897 | def __set_log_file_name(self): 898 | """Automatically set logFileName attribute""" 899 | # ensure directory exists 900 | dir, _ = os.path.split(self.__logFileBasename) 901 | if len(dir) and not os.path.exists(dir): 902 | os.makedirs(dir) 903 | # get existing logfiles 904 | numsLUT = {} 905 | filesLUT = {} 906 | ordered = [] 907 | if not len(dir) or os.path.isdir(dir): 908 | listDir = os.listdir(dir) if len(dir) else os.listdir('.') 909 | for f in listDir: 910 | p = os.path.join(dir,f) 911 | if not os.path.isfile(p): 912 | continue 913 | if re.match("^{bsn}(_\\d+)?.{ext}$".format(bsn=self.__logFileBasename, ext=self.__logFileExtension), p) is None: 914 | continue 915 | n = p.split(self.__logFileBasename)[1].split('.%s'%self.__logFileExtension)[0] 916 | n = int(n[1:]) if len(n) else '' 917 | assert n not in numsLUT , "filelog number is found in LUT shouldn't have happened. PLEASE REPORT BUG" 918 | numsLUT[n] = p 919 | filesLUT[p] = n 920 | ordered = ([''] if '' in numsLUT else []) + sorted([n for n in numsLUT if isinstance(n, int)]) 921 | ordered = [numsLUT[n] for n in ordered] 922 | # get last file number 923 | if len(ordered): 924 | number = filesLUT[ordered[-1]] 925 | else: 926 | number = self.__logFileFirstNumber 927 | # limit number of log files to logFileRoll 928 | if self.__logFileRoll is not None: 929 | while len(ordered)>self.__logFileRoll: 930 | os.remove(ordered.pop(0)) 931 | if len(ordered) == self.__logFileRoll and self.__logFileMaxSize is not None: 932 | if os.stat(ordered[-1]).st_size/(1024.**2) >= self.__logFileMaxSize: 933 | #if os.stat(ordered[-1]).st_size/1e6 >= self.__logFileMaxSize: 934 | os.remove(ordered.pop(0)) 935 | if isinstance(number, int): 936 | number = number + 1 937 | # temporarily set self.__logFileName 938 | if not isinstance(number, int): 939 | self.__logFileName = self.__logFileBasename+"."+self.__logFileExtension 940 | number = -1 941 | else: 942 | self.__logFileName = self.__logFileBasename+"_"+str(number)+"."+self.__logFileExtension 943 | # check temporarily set logFileName file size 944 | if self.__logFileMaxSize is not None: 945 | while os.path.isfile(self.__logFileName): 946 | if os.stat(self.__logFileName).st_size/(1024.**2) < self.__logFileMaxSize: 947 | #if os.stat(self.__logFileName).st_size/1e6 < self.__logFileMaxSize: 948 | break 949 | number += 1 950 | self.__logFileName = self.__logFileBasename+"_"+str(number)+"."+self.__logFileExtension 951 | # create log file stream 952 | if self.__logFileStream is not None: 953 | try: 954 | self.__logFileStream.close() 955 | except: 956 | pass 957 | self.__logFileStream = None 958 | 959 | def set_log_file_maximum_size(self, logFileMaxSize): 960 | """ 961 | Set the log file maximum size in megabytes 962 | 963 | :Parameters: 964 | #. logFileMaxSize (None, number): The maximum size in Megabytes 965 | of a logging file. Once exceeded, another logging file as 966 | logFileBasename_N.logFileExtension will be created. 967 | Where N is an automatically incremented number. If None or a 968 | negative number is given, the logging file will grow 969 | indefinitely 970 | """ 971 | if logFileMaxSize is not None: 972 | assert _is_number(logFileMaxSize), "logFileMaxSize must be a number" 973 | logFileMaxSize = float(logFileMaxSize) 974 | if logFileMaxSize <=0: 975 | logFileMaxSize = None 976 | #assert logFileMaxSize>=1, "logFileMaxSize minimum size is 1 megabytes" 977 | self.__logFileMaxSize = logFileMaxSize 978 | 979 | def set_log_file_first_number(self, logFileFirstNumber): 980 | """ 981 | Set log file first number 982 | 983 | :Parameters: 984 | #. logFileFirstNumber (None, integer): first log file number 'N' in 985 | logFileBasename_N.logFileExtension. If None is given then 986 | first log file will be logFileBasename.logFileExtension and ince 987 | logFileMaxSize is reached second log file will be 988 | logFileBasename_0.logFileExtension and so on and so forth. 989 | If number is given it must be an integer >=0 990 | """ 991 | if logFileFirstNumber is not None: 992 | assert isinstance(logFileFirstNumber, int), "logFileFirstNumber must be None or an integer" 993 | assert logFileFirstNumber>=0, "logFileFirstNumber integer must be >=0" 994 | self.__logFileFirstNumber = logFileFirstNumber 995 | 996 | def set_minimum_level(self, level=0, stdoutFlag=True, fileFlag=True): 997 | """ 998 | Set the minimum logging level. All levels below the minimum will be ignored at logging. 999 | 1000 | :Parameters: 1001 | #. level (None, number, str): The minimum level of logging. 1002 | If None, minimum level checking is left out. 1003 | If str, it must be a defined logtype and therefore the minimum level would be the level of this logtype. 1004 | #. stdoutFlag (boolean): Whether to apply this minimum level to standard output logging. 1005 | #. fileFlag (boolean): Whether to apply this minimum level to file logging. 1006 | """ 1007 | # check flags 1008 | assert isinstance(stdoutFlag, bool), "stdoutFlag must be boolean" 1009 | assert isinstance(fileFlag, bool), "fileFlag must be boolean" 1010 | if not (stdoutFlag or fileFlag): 1011 | return 1012 | # check level 1013 | if level is not None: 1014 | if isinstance(level, basestring): 1015 | level = str(level) 1016 | assert level in self.__logTypeStdoutFlags, "level '%s' given as string, is not defined logType" %level 1017 | level = self.__logTypeLevels[level] 1018 | assert _is_number(level), "level must be a number" 1019 | level = float(level) 1020 | if stdoutFlag: 1021 | if self.__stdoutMaxLevel is not None: 1022 | assert level<=self.__stdoutMaxLevel, "stdoutMinLevel must be smaller or equal to stdoutMaxLevel %s"%self.__stdoutMaxLevel 1023 | if fileFlag: 1024 | if self.__fileMaxLevel is not None: 1025 | assert level<=self.__fileMaxLevel, "fileMinLevel must be smaller or equal to fileMaxLevel %s"%self.__fileMaxLevel 1026 | # set flags 1027 | if stdoutFlag: 1028 | self.__stdoutMinLevel = level 1029 | self.__update_stdout_flags() 1030 | if fileFlag: 1031 | self.__fileMinLevel = level 1032 | self.__update_file_flags() 1033 | 1034 | def set_maximum_level(self, level=0, stdoutFlag=True, fileFlag=True): 1035 | """ 1036 | Set the maximum logging level. All levels above the maximum will be ignored at logging. 1037 | 1038 | :Parameters: 1039 | #. level (None, number, str): The maximum level of logging. 1040 | If None, maximum level checking is left out. 1041 | If str, it must be a defined logtype and therefore the maximum level would be the level of this logtype. 1042 | #. stdoutFlag (boolean): Whether to apply this maximum level to standard output logging. 1043 | #. fileFlag (boolean): Whether to apply this maximum level to file logging. 1044 | """ 1045 | # check flags 1046 | assert isinstance(stdoutFlag, bool), "stdoutFlag must be boolean" 1047 | assert isinstance(fileFlag, bool), "fileFlag must be boolean" 1048 | if not (stdoutFlag or fileFlag): 1049 | return 1050 | # check level 1051 | if level is not None: 1052 | if isinstance(level, basestring): 1053 | level = str(level) 1054 | assert level in self.__logTypeStdoutFlags, "level '%s' given as string, is not defined logType"%level 1055 | level = self.__logTypeLevels[level] 1056 | assert _is_number(level), "level must be a number" 1057 | level = float(level) 1058 | if stdoutFlag: 1059 | if self.__stdoutMinLevel is not None: 1060 | assert level>=self.__stdoutMinLevel, "stdoutMaxLevel must be bigger or equal to stdoutMinLevel %s"%self.__stdoutMinLevel 1061 | if fileFlag: 1062 | if self.__fileMinLevel is not None: 1063 | assert level>=self.__fileMinLevel, "fileMaxLevel must be bigger or equal to fileMinLevel %s"%self.__fileMinLevel 1064 | # set flags 1065 | if stdoutFlag: 1066 | self.__stdoutMaxLevel = level 1067 | self.__update_stdout_flags() 1068 | if fileFlag: 1069 | self.__fileMaxLevel = level 1070 | self.__update_file_flags() 1071 | 1072 | def __update_flags(self): 1073 | self.__update_stdout_flags() 1074 | self.__update_file_flags() 1075 | 1076 | def __update_stdout_flags(self): 1077 | # set stdoutMinLevel 1078 | stdoutkeys = list(self.__forcedStdoutLevels) 1079 | if self.__stdoutMinLevel is not None: 1080 | for logType, l in self.__logTypeLevels.items(): 1081 | if logType not in stdoutkeys: 1082 | self.__logTypeStdoutFlags[logType] = l>=self.__stdoutMinLevel 1083 | # set stdoutMaxLevel 1084 | if self.__stdoutMaxLevel is not None: 1085 | for logType, l in self.__logTypeLevels.items(): 1086 | if logType not in stdoutkeys: 1087 | self.__logTypeStdoutFlags[logType] = l<=self.__stdoutMaxLevel 1088 | # when stdout min and max are None 1089 | if (self.__stdoutMinLevel is None) and (self.__stdoutMaxLevel is None): 1090 | for logType, l in self.__logTypeLevels.items(): 1091 | if logType not in stdoutkeys: 1092 | self.__logTypeStdoutFlags[logType] = True 1093 | 1094 | def __update_file_flags(self): 1095 | # set fileMinLevel 1096 | filekeys = list(self.__forcedFileLevels) 1097 | if self.__fileMinLevel is not None: 1098 | for logType, l in self.__logTypeLevels.items(): 1099 | if logType not in filekeys: 1100 | self.__logTypeFileFlags[logType] = l>=self.__fileMinLevel 1101 | # set fileMaxLevel 1102 | if self.__fileMaxLevel is not None: 1103 | for logType, l in self.__logTypeLevels.items(): 1104 | if logType not in filekeys: 1105 | self.__logTypeFileFlags[logType] = l<=self.__fileMaxLevel 1106 | # when file min and max are None 1107 | if (self.__fileMinLevel is None) and (self.__fileMaxLevel is None): 1108 | for logType, l in self.__logTypeLevels.items(): 1109 | if logType not in filekeys: 1110 | self.__logTypeFileFlags[logType] = True 1111 | 1112 | def force_log_type_stdout_flag(self, logType, flag): 1113 | """ 1114 | Force a logtype standard output logging flag despite minimum and maximum logging level boundaries. 1115 | 1116 | :Parameters: 1117 | #. logType (string): A defined logging type. 1118 | #. flag (None boolean): The standard output logging flag. 1119 | If None, logtype existing forced flag is released. 1120 | """ 1121 | assert logType in self.__logTypeStdoutFlags, "logType '%s' not defined" %logType 1122 | if flag is None: 1123 | self.__forcedStdoutLevels.pop(logType, None) 1124 | self.__update_stdout_flags() 1125 | else: 1126 | assert isinstance(flag, bool), "flag must be boolean" 1127 | self.__logTypeStdoutFlags[logType] = flag 1128 | self.__forcedStdoutLevels[logType] = flag 1129 | 1130 | def force_log_type_file_flag(self, logType, flag): 1131 | """ 1132 | Force a logtype file logging flag despite minimum and maximum logging level boundaries. 1133 | 1134 | :Parameters: 1135 | #. logType (string): A defined logging type. 1136 | #. flag (None, boolean): The file logging flag. 1137 | If None, logtype existing forced flag is released. 1138 | """ 1139 | assert logType in self.__logTypeStdoutFlags, "logType '%s' not defined" %logType 1140 | if flag is None: 1141 | self.__forcedFileLevels.pop(logType, None) 1142 | self.__update_file_flags() 1143 | else: 1144 | assert isinstance(flag, bool), "flag must be boolean" 1145 | self.__logTypeFileFlags[logType] = flag 1146 | self.__forcedFileLevels[logType] = flag 1147 | 1148 | def force_log_type_flags(self, logType, stdoutFlag, fileFlag): 1149 | """ 1150 | Force a logtype logging flags. 1151 | 1152 | :Parameters: 1153 | #. logType (string): A defined logging type. 1154 | #. stdoutFlag (None, boolean): The standard output logging flag. 1155 | If None, logtype stdoutFlag forcing is released. 1156 | #. fileFlag (None, boolean): The file logging flag. 1157 | If None, logtype fileFlag forcing is released. 1158 | """ 1159 | self.force_log_type_stdout_flag(logType, stdoutFlag) 1160 | self.force_log_type_file_flag(logType, fileFlag) 1161 | 1162 | def set_log_type_name(self, logType, name): 1163 | """ 1164 | Set a logtype name. 1165 | 1166 | :Parameters: 1167 | #. logType (string): A defined logging type. 1168 | #. name (string): The logtype new name. 1169 | """ 1170 | assert logType in self.__logTypeStdoutFlags, "logType '%s' not defined" %logType 1171 | assert isinstance(name, basestring), "name must be a string" 1172 | name = str(name) 1173 | self.__logTypeNames[logType] = name 1174 | 1175 | def set_log_type_level(self, logType, level): 1176 | """ 1177 | Set a logtype logging level. 1178 | 1179 | :Parameters: 1180 | #. logType (string): A defined logging type. 1181 | #. level (number): The level of logging. 1182 | """ 1183 | assert _is_number(level), "level must be a number" 1184 | level = float(level) 1185 | name = str(name) 1186 | self.__logTypeLevels[logType] = level 1187 | 1188 | def remove_log_type(self, logType, _assert=False): 1189 | """ 1190 | Remove a logtype. 1191 | 1192 | :Parameters: 1193 | #. logType (string): The logtype. 1194 | #. _assert (boolean): Raise an assertion error if logType is not defined. 1195 | """ 1196 | # check logType 1197 | if _assert: 1198 | assert logType in self.__logTypeStdoutFlags, "logType '%s' is not defined" %logType 1199 | # remove logType 1200 | self.__logTypeColor.pop(logType) 1201 | self.__logTypeHighlight.pop(logType) 1202 | self.__logTypeAttributes.pop(logType) 1203 | self.__logTypeNames.pop(logType) 1204 | self.__logTypeLevels.pop(logType) 1205 | self.__logTypeFormat.pop(logType) 1206 | self.__logTypeStdoutFlags.pop(logType) 1207 | self.__logTypeFileFlags.pop(logType) 1208 | self.__forcedStdoutLevels.pop(logType, None) 1209 | self.__forcedFileLevels.pop(logType, None) 1210 | 1211 | def add_log_type(self, logType, name=None, level=0, stdoutFlag=None, fileFlag=None, color=None, highlight=None, attributes=None): 1212 | """ 1213 | Add a new logtype. 1214 | 1215 | :Parameters: 1216 | #. logType (string): The logtype. 1217 | #. name (None, string): The logtype name. If None, name will be set to logtype. 1218 | #. level (number): The level of logging. 1219 | #. stdoutFlag (None, boolean): Force standard output logging flag. 1220 | If None, flag will be set according to minimum and maximum levels. 1221 | #. fileFlag (None, boolean): Force file logging flag. 1222 | If None, flag will be set according to minimum and maximum levels. 1223 | #. color (None, string): The logging text color. The defined colors are:\n 1224 | black , red , green , orange , blue , magenta , cyan , grey , dark grey , 1225 | light red , light green , yellow , light blue , pink , light cyan 1226 | #. highlight (None, string): The logging text highlight color. The defined highlights are:\n 1227 | black , red , green , orange , blue , magenta , cyan , grey 1228 | #. attributes (None, string): The logging text attribute. The defined attributes are:\n 1229 | bold , underline , blink , invisible , strike through 1230 | 1231 | **N.B** *logging color, highlight and attributes are not allowed on all types of streams.* 1232 | """ 1233 | # check logType 1234 | assert logType not in self.__logTypeStdoutFlags, "logType '%s' already defined" %logType 1235 | assert isinstance(logType, basestring), "logType must be a string" 1236 | logType = str(logType) 1237 | # set log type 1238 | self.__set_log_type(logType=logType, name=name, level=level, 1239 | stdoutFlag=stdoutFlag, fileFlag=fileFlag, 1240 | color=color, highlight=highlight, attributes=attributes) 1241 | 1242 | 1243 | def __set_log_type(self, logType, name, level, stdoutFlag, fileFlag, color, highlight, attributes): 1244 | # check name 1245 | if name is None: 1246 | name = logType 1247 | assert isinstance(name, basestring), "name must be a string" 1248 | name = str(name) 1249 | # check level 1250 | assert _is_number(level), "level must be a number" 1251 | level = float(level) 1252 | # check color 1253 | if color is not None: 1254 | assert color in self.__stdoutFontFormat["color"], "color %s not known"%str(color) 1255 | # check highlight 1256 | if highlight is not None: 1257 | assert highlight in self.__stdoutFontFormat["highlight"], "highlight %s not known"%str(highlight) 1258 | # check attributes 1259 | if attributes is not None: 1260 | for attr in attributes: 1261 | assert attr in self.__stdoutFontFormat["attributes"], "attribute %s not known"%str(attr) 1262 | # check flags 1263 | if stdoutFlag is not None: 1264 | assert isinstance(stdoutFlag, bool), "stdoutFlag must be boolean" 1265 | if fileFlag is not None: 1266 | assert isinstance(fileFlag, bool), "fileFlag must be boolean" 1267 | # set wrapFancy 1268 | wrapFancy=["",""] 1269 | if color is not None: 1270 | code = self.__stdoutFontFormat["color"][color] 1271 | if len(code): 1272 | code = ";"+code 1273 | wrapFancy[0] += code 1274 | if highlight is not None: 1275 | code = self.__stdoutFontFormat["highlight"][highlight] 1276 | if len(code): 1277 | code = ";"+code 1278 | wrapFancy[0] += code 1279 | if attributes is None: 1280 | attributes = [] 1281 | elif isinstance(attributes, basestring): 1282 | attributes = [str(attributes)] 1283 | for attr in attributes: 1284 | code = self.__stdoutFontFormat["attributes"][attr] 1285 | if len(code): 1286 | code = ";"+code 1287 | wrapFancy[0] += code 1288 | if len(wrapFancy[0]): 1289 | wrapFancy = ["\033["+self.__stdoutFontFormat["reset"]+wrapFancy[0]+"m" , "\033["+self.__stdoutFontFormat["reset"]+"m" ] 1290 | # add logType 1291 | self.__logTypeColor[logType] = color 1292 | self.__logTypeHighlight[logType] = highlight 1293 | self.__logTypeAttributes[logType] = attributes 1294 | self.__logTypeNames[logType] = name 1295 | self.__logTypeLevels[logType] = level 1296 | self.__logTypeFormat[logType] = wrapFancy 1297 | self.__logTypeStdoutFlags[logType] = stdoutFlag 1298 | if stdoutFlag is not None: 1299 | self.__forcedStdoutLevels[logType] = stdoutFlag 1300 | else: 1301 | self.__forcedStdoutLevels.pop(logType, None) 1302 | self.__update_stdout_flags() 1303 | self.__logTypeFileFlags[logType] = fileFlag 1304 | if fileFlag is not None: 1305 | self.__forcedFileLevels[logType] = fileFlag 1306 | else: 1307 | self.__forcedFileLevels.pop(logType, None) 1308 | self.__update_file_flags() 1309 | 1310 | def update_log_type(self, logType, name=None, level=None, stdoutFlag=None, fileFlag=None, color=None, highlight=None, attributes=None): 1311 | """ 1312 | update a logtype. 1313 | 1314 | :Parameters: 1315 | #. logType (string): The logtype. 1316 | #. name (None, string): The logtype name. If None, name will be set to logtype. 1317 | #. level (number): The level of logging. 1318 | #. stdoutFlag (None, boolean): Force standard output logging flag. 1319 | If None, flag will be set according to minimum and maximum levels. 1320 | #. fileFlag (None, boolean): Force file logging flag. 1321 | If None, flag will be set according to minimum and maximum levels. 1322 | #. color (None, string): The logging text color. The defined colors are:\n 1323 | black , red , green , orange , blue , magenta , cyan , grey , dark grey , 1324 | light red , light green , yellow , light blue , pink , light cyan 1325 | #. highlight (None, string): The logging text highlight color. The defined highlights are:\n 1326 | black , red , green , orange , blue , magenta , cyan , grey 1327 | #. attributes (None, string): The logging text attribute. The defined attributes are:\n 1328 | bold , underline , blink , invisible , strike through 1329 | 1330 | **N.B** *logging color, highlight and attributes are not allowed on all types of streams.* 1331 | """ 1332 | # check logType 1333 | assert logType in self.__logTypeStdoutFlags, "logType '%s' is not defined" %logType 1334 | # get None updates 1335 | if name is None: name = self.__logTypeNames[logType] 1336 | if level is None: level = self.__logTypeLevels[logType] 1337 | if stdoutFlag is None: stdoutFlag = self.__logTypeStdoutFlags[logType] 1338 | if fileFlag is None: fileFlag = self.__logTypeFileFlags[logType] 1339 | if color is None: color = self.__logTypeColor[logType] 1340 | if highlight is None: highlight = self.__logTypeHighlight[logType] 1341 | if attributes is None: attributes = self.__logTypeAttributes[logType] 1342 | # update log type 1343 | self.__set_log_type(logType=logType, name=name, level=level, 1344 | stdoutFlag=stdoutFlag, fileFlag=fileFlag, 1345 | color=color, highlight=highlight, attributes=attributes) 1346 | 1347 | def _format_message(self, logType, message, data, tback): 1348 | header = self._get_header(logType, message) 1349 | footer = self._get_footer(logType, message) 1350 | dataStr = '' 1351 | tbackStr = '' 1352 | if data is not None: 1353 | #dataStr = '\n%s'%(repr(data)) 1354 | dataStr = '\n%s'%(data,) 1355 | if tback is not None: 1356 | if isinstance(tback, str): 1357 | tbackStr = '\n%s'%(tback,) 1358 | else: 1359 | try: 1360 | tbackStr = [] 1361 | for filename, lineno, name, line in tback: 1362 | tbackStr.append( '\n File "%s", line %d, in %s'%(filename,lineno,name) ) 1363 | if line: 1364 | tbackStr.append( '\n %s'%(line.strip(),) ) 1365 | tbackStr = ''.join(tbackStr) 1366 | except: 1367 | tbackStr = '\n%s'%(str(tback),) 1368 | return "%s%s%s%s%s" %(header, message, footer, dataStr, tbackStr) 1369 | 1370 | def _get_datetimestamp(self, format='%Y-%m-%d %H:%M:%S'): 1371 | return datetime.strftime(datetime.now(self.__timezone), format) 1372 | 1373 | def _get_header(self, logType, message): 1374 | dateTime = datetime.strftime(datetime.now(self.__timezone), '%Y-%m-%d %H:%M:%S') 1375 | return "%s - %s <%s> "%(dateTime, self.__name, self.__logTypeNames[logType]) 1376 | 1377 | def _get_footer(self, logType, message): 1378 | return "" 1379 | 1380 | def __log_to_file(self, message): 1381 | # create log file stream 1382 | if self.__logFileStream is None: 1383 | self.__logFileStream = open(self.__logFileName, 'a') 1384 | elif self.__logFileMaxSize is not None: 1385 | if self.__logFileStream.tell()/(1024.**2) >= self.__logFileMaxSize: 1386 | #if self.__logFileStream.tell()/1e6 >= self.__logFileMaxSize: 1387 | self.__set_log_file_name() 1388 | self.__logFileStream = open(self.__logFileName, 'a') 1389 | # log to file 1390 | # no need to try and catch. even when main thread dies a file handler 1391 | # shouldn't close 1392 | self.__logFileStream.write(message) 1393 | 1394 | def __log_to_stdout(self, message): 1395 | try: 1396 | self.__stdout.write(message) 1397 | except: 1398 | # for the rare case when stdout buffer no more exits. 1399 | # this can happen when main thread dies and all remaining threads 1400 | # turn to daemon threads. Try and catch add absolutely no 1401 | # overhead unless when an error occurs. 1402 | pass 1403 | 1404 | def is_enabled_for_stdout(self, logType): 1405 | """Get whether given logtype is enabled for standard output logging. 1406 | When a logType is not enabled, calling for log will return without 1407 | logging. 1408 | This method will check general standard output logging flag and given 1409 | logType standard output flag. For a logType to log it must have both 1410 | flags set to True 1411 | 1412 | :Parameters: 1413 | #. logType (string): A defined logging type. 1414 | 1415 | :Returns: 1416 | #. enabled (bool): whehter enabled or not. 1417 | """ 1418 | return self.__logToStdout and self.__logTypeStdoutFlags[logType] 1419 | 1420 | def is_enabled_for_file(self, logType): 1421 | """Get whether given logtype is enabled for file logging. 1422 | When a logType is not enabled, calling for log will return without 1423 | logging. 1424 | This method will check general file logging flag and given 1425 | logType file flag. For a logType to log it must have both 1426 | flags set to True 1427 | 1428 | :Parameters: 1429 | #. logType (string): A defined logging type. 1430 | 1431 | :Returns: 1432 | #. enabled (bool): whehter enabled or not. 1433 | """ 1434 | return self.__logToFile and self.__logTypeFileFlags[logType] 1435 | 1436 | 1437 | def log(self, logType, message, data=None, tback=None, countConstraint=None): 1438 | """ 1439 | log a message of a certain logtype. 1440 | 1441 | :Parameters: 1442 | #. logType (string): A defined logging type. 1443 | #. message (string): Any message to log. 1444 | #. countConstraint (None, number): maximum number of time to log 1445 | the given message 1446 | #. data (None, object): Any type of data to print and/or write to log file 1447 | after log message 1448 | #. tback (None, str, list): Stack traceback to print and/or write to 1449 | log file. In general, this should be traceback.extract_stack 1450 | 1451 | :Returns: 1452 | #. message (string): the logged message 1453 | """ 1454 | if countConstraint is not None: 1455 | self.__logMessagesCounter.setdefault(message, -1) 1456 | self.__logMessagesCounter[message] += 1 1457 | if countConstraint<=self.__logMessagesCounter[message]: 1458 | return message 1459 | # log to stdout 1460 | log = self._format_message(logType=logType, message=message, data=data, tback=tback) 1461 | if self.__logToStdout and self.__logTypeStdoutFlags[logType]: 1462 | #self.__log_to_stdout(self.__logTypeFormat[logType][0] + log + self.__logTypeFormat[logType][1] + "\n") 1463 | self.__log_to_stdout("%s%s%s\n"%(self.__logTypeFormat[logType][0],log,self.__logTypeFormat[logType][1])) 1464 | if self.__flush: 1465 | try: 1466 | self.__stdout.flush() 1467 | except: 1468 | pass 1469 | try: 1470 | os.fsync(self.__stdout.fileno()) 1471 | except: 1472 | pass 1473 | # log to file 1474 | if self.__logToFile and self.__logTypeFileFlags[logType]: 1475 | self.__log_to_file("%s\n"%log) 1476 | if self.__flush: 1477 | try: 1478 | self.__logFileStream.flush() 1479 | except: 1480 | pass 1481 | try: 1482 | os.fsync(self.__logFileStream.fileno()) 1483 | except: 1484 | pass 1485 | # set last logged message 1486 | self.__lastLogged[logType] = log 1487 | self.__lastLogged[-1] = log 1488 | # always return logged message 1489 | return message 1490 | 1491 | def force_log(self, logType, message, data=None, tback=None, stdout=True, file=True): 1492 | """ 1493 | Force logging a message of a certain logtype whether logtype level is allowed or not. 1494 | 1495 | :Parameters: 1496 | #. logType (string): A defined logging type. 1497 | #. message (string): Any message to log. 1498 | #. tback (None, str, list): Stack traceback to print and/or write to 1499 | log file. In general, this should be traceback.extract_stack 1500 | #. stdout (boolean): Whether to force logging to standard output. 1501 | #. file (boolean): Whether to force logging to file. 1502 | 1503 | :Returns: 1504 | #. message (string): the logged message 1505 | """ 1506 | # log to stdout 1507 | log = self._format_message(logType=logType, message=message, data=data, tback=tback) 1508 | if stdout: 1509 | #self.__log_to_stdout(self.__logTypeFormat[logType][0] + log + self.__logTypeFormat[logType][1] + "\n") 1510 | self.__log_to_stdout("%s%s%s\n"%(self.__logTypeFormat[logType][0],log,self.__logTypeFormat[logType][1])) 1511 | try: 1512 | self.__stdout.flush() 1513 | except: 1514 | pass 1515 | try: 1516 | os.fsync(self.__stdout.fileno()) 1517 | except: 1518 | pass 1519 | if file: 1520 | # log to file 1521 | self.__log_to_file("%s\n"%log) 1522 | try: 1523 | self.__logFileStream.flush() 1524 | except: 1525 | pass 1526 | try: 1527 | os.fsync(self.__logFileStream.fileno()) 1528 | except: 1529 | pass 1530 | # set last logged message 1531 | self.__lastLogged[logType] = log 1532 | self.__lastLogged[-1] = log 1533 | # always return logged message 1534 | return message 1535 | 1536 | def flush(self): 1537 | """Flush all streams.""" 1538 | if self.__logFileStream is not None: 1539 | try: 1540 | self.__logFileStream.flush() 1541 | except: 1542 | pass 1543 | try: 1544 | os.fsync(self.__logFileStream.fileno()) 1545 | except: 1546 | pass 1547 | if self.__stdout is not None: 1548 | try: 1549 | self.__stdout.flush() 1550 | except: 1551 | pass 1552 | try: 1553 | os.fsync(self.__stdout.fileno()) 1554 | except: 1555 | pass 1556 | 1557 | def info(self, message, *args, **kwargs): 1558 | """alias to message at information level""" 1559 | return self.log("info", message, *args, **kwargs) 1560 | 1561 | def information(self, message, *args, **kwargs): 1562 | """alias to message at information level""" 1563 | return self.log("info", message, *args, **kwargs) 1564 | 1565 | def warn(self, message, *args, **kwargs): 1566 | """alias to message at warning level""" 1567 | return self.log("warn", message, *args, **kwargs) 1568 | 1569 | def warning(self, message, *args, **kwargs): 1570 | """alias to message at warning level""" 1571 | return self.log("warn", message, *args, **kwargs) 1572 | 1573 | def error(self, message, *args, **kwargs): 1574 | """alias to message at error level""" 1575 | return self.log("error", message, *args, **kwargs) 1576 | 1577 | def critical(self, message, *args, **kwargs): 1578 | """alias to message at critical level""" 1579 | return self.log("critical", message, *args, **kwargs) 1580 | 1581 | def debug(self, message, *args, **kwargs): 1582 | """alias to message at debug level""" 1583 | return self.log("debug", message, *args, **kwargs) 1584 | 1585 | 1586 | 1587 | class SingleLogger(Logger): 1588 | """ 1589 | This is singleton implementation of Logger class. 1590 | """ 1591 | __thisInstance = None 1592 | def __new__(cls, *args, **kwds): 1593 | if cls.__thisInstance is None: 1594 | cls.__thisInstance = super(SingleLogger,cls).__new__(cls) 1595 | cls.__thisInstance._isInitialized = False 1596 | return cls.__thisInstance 1597 | 1598 | def __init__(self, *args, **kwargs): 1599 | if (self._isInitialized): return 1600 | # initialize 1601 | super(SingleLogger, self).__init__(*args, **kwargs) 1602 | # update flag 1603 | self._isInitialized = True 1604 | 1605 | 1606 | 1607 | if __name__ == "__main__": 1608 | import time 1609 | l=Logger("log test") 1610 | l.add_log_type("super critical", name="SUPER CRITICAL", level=200, color='red', attributes=["bold","underline"]) 1611 | l.add_log_type("wrong", name="info", color='magenta', attributes=["strike through"]) 1612 | l.add_log_type("important", name="info", color='black', highlight="orange", attributes=["bold","blink"]) 1613 | print(l, '\n') 1614 | # print available logs and logging time. 1615 | for logType in l.logTypes: 1616 | tic = time.time() 1617 | l.log(logType, "this is '%s' level log message."%logType) 1618 | print("%s seconds\n"%str(time.time()-tic)) 1619 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is a pythonic simple yet complete system logger. It allows logging simultaneously 3 | to two streams, the first one is the system standard output by default and the second 4 | one is designated to be set to a file. In addition, pysimplelog is text colouring 5 | and attributes enabled when the stream allows it. 6 | 7 | 8 | Installation guide: 9 | =================== 10 | pysimplelog is a pure python 2.7.x module that needs no particular installation. 11 | One can either fork pysimplelog's `github repository 12 | `_ and copy the 13 | package to python's site-packages or use pip as the following: 14 | 15 | 16 | .. code-block:: console 17 | 18 | 19 | pip install pysimplelog 20 | """ 21 | 22 | try: 23 | from .__pkginfo__ import __version__, __author__, __email__, __onlinedoc__, __repository__, __pypi__ 24 | from .SimpleLog import Logger, SingleLogger 25 | except: 26 | from __pkginfo__ import __version__, __author__, __email__, __onlinedoc__, __repository__, __pypi__ 27 | from SimpleLog import Logger, SingleLogger 28 | 29 | 30 | def get_version(): 31 | """Get pysimplelog's version number.""" 32 | return __version__ 33 | 34 | def get_author(): 35 | """Get pysimplelog's author's name.""" 36 | return __author__ 37 | 38 | def get_email(): 39 | """Get pysimplelog's author's email.""" 40 | return __email__ 41 | 42 | def get_doc(): 43 | """Get pysimplelog's official online documentation link.""" 44 | return __onlinedoc__ 45 | 46 | def get_repository(): 47 | """Get pysimplelog's official online repository link.""" 48 | return __repository__ 49 | 50 | def get_pypi(): 51 | """Get pysimplelog pypi's link.""" 52 | return __pypi__ 53 | -------------------------------------------------------------------------------- /__pkginfo__.py: -------------------------------------------------------------------------------- 1 | __version__ = '4.0.0' 2 | 3 | __author__ = "Bachir Aoun" 4 | 5 | __email__ = "bachir.aoun@e-aoun.com" 6 | 7 | __onlinedoc__ = "http://bachiraoun.github.io/pysimplelog" 8 | 9 | __repository__ = "https://github.com/bachiraoun/pysimplelog" 10 | 11 | __pypi__ = "https://pypi.python.org/pypi/pysimplelog" 12 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 16 | 17 | .PHONY: help 18 | help: 19 | @echo "Please use \`make ' where is one of" 20 | @echo " html to make standalone HTML files" 21 | @echo " dirhtml to make HTML files named index.html in directories" 22 | @echo " singlehtml to make a single large HTML file" 23 | @echo " pickle to make pickle files" 24 | @echo " json to make JSON files" 25 | @echo " htmlhelp to make HTML files and a HTML help project" 26 | @echo " qthelp to make HTML files and a qthelp project" 27 | @echo " applehelp to make an Apple Help Book" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " epub3 to make an epub3" 31 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 32 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 33 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 34 | @echo " text to make text files" 35 | @echo " man to make manual pages" 36 | @echo " texinfo to make Texinfo files" 37 | @echo " info to make Texinfo files and run them through makeinfo" 38 | @echo " gettext to make PO message catalogs" 39 | @echo " changes to make an overview of all changed/added/deprecated items" 40 | @echo " xml to make Docutils-native XML files" 41 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 42 | @echo " linkcheck to check all external links for integrity" 43 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 44 | @echo " coverage to run coverage check of the documentation (if enabled)" 45 | @echo " dummy to check syntax errors of document sources" 46 | 47 | .PHONY: clean 48 | clean: 49 | rm -rf $(BUILDDIR)/* 50 | 51 | .PHONY: html 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | .PHONY: dirhtml 58 | dirhtml: 59 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 60 | @echo 61 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 62 | 63 | .PHONY: singlehtml 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | .PHONY: pickle 70 | pickle: 71 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 72 | @echo 73 | @echo "Build finished; now you can process the pickle files." 74 | 75 | .PHONY: json 76 | json: 77 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 78 | @echo 79 | @echo "Build finished; now you can process the JSON files." 80 | 81 | .PHONY: htmlhelp 82 | htmlhelp: 83 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 84 | @echo 85 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 86 | ".hhp project file in $(BUILDDIR)/htmlhelp." 87 | 88 | .PHONY: qthelp 89 | qthelp: 90 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 91 | @echo 92 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 93 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 94 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pypref.qhcp" 95 | @echo "To view the help file:" 96 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pypref.qhc" 97 | 98 | .PHONY: applehelp 99 | applehelp: 100 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 101 | @echo 102 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 103 | @echo "N.B. You won't be able to view it unless you put it in" \ 104 | "~/Library/Documentation/Help or install it in your application" \ 105 | "bundle." 106 | 107 | .PHONY: devhelp 108 | devhelp: 109 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 110 | @echo 111 | @echo "Build finished." 112 | @echo "To view the help file:" 113 | @echo "# mkdir -p $$HOME/.local/share/devhelp/pypref" 114 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pypref" 115 | @echo "# devhelp" 116 | 117 | .PHONY: epub 118 | epub: 119 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 120 | @echo 121 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 122 | 123 | .PHONY: epub3 124 | epub3: 125 | $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 126 | @echo 127 | @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." 128 | 129 | .PHONY: latex 130 | latex: 131 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 132 | @echo 133 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 134 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 135 | "(use \`make latexpdf' here to do that automatically)." 136 | 137 | .PHONY: latexpdf 138 | latexpdf: 139 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 140 | @echo "Running LaTeX files through pdflatex..." 141 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 142 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 143 | 144 | .PHONY: latexpdfja 145 | latexpdfja: 146 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 147 | @echo "Running LaTeX files through platex and dvipdfmx..." 148 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 149 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 150 | 151 | .PHONY: text 152 | text: 153 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 154 | @echo 155 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 156 | 157 | .PHONY: man 158 | man: 159 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 160 | @echo 161 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 162 | 163 | .PHONY: texinfo 164 | texinfo: 165 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 166 | @echo 167 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 168 | @echo "Run \`make' in that directory to run these through makeinfo" \ 169 | "(use \`make info' here to do that automatically)." 170 | 171 | .PHONY: info 172 | info: 173 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 174 | @echo "Running Texinfo files through makeinfo..." 175 | make -C $(BUILDDIR)/texinfo info 176 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 177 | 178 | .PHONY: gettext 179 | gettext: 180 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 181 | @echo 182 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 183 | 184 | .PHONY: changes 185 | changes: 186 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 187 | @echo 188 | @echo "The overview file is in $(BUILDDIR)/changes." 189 | 190 | .PHONY: linkcheck 191 | linkcheck: 192 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 193 | @echo 194 | @echo "Link check complete; look for any errors in the above output " \ 195 | "or in $(BUILDDIR)/linkcheck/output.txt." 196 | 197 | .PHONY: doctest 198 | doctest: 199 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 200 | @echo "Testing of doctests in the sources finished, look at the " \ 201 | "results in $(BUILDDIR)/doctest/output.txt." 202 | 203 | .PHONY: coverage 204 | coverage: 205 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 206 | @echo "Testing of coverage in the sources finished, look at the " \ 207 | "results in $(BUILDDIR)/coverage/python.txt." 208 | 209 | .PHONY: xml 210 | xml: 211 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 212 | @echo 213 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 214 | 215 | .PHONY: pseudoxml 216 | pseudoxml: 217 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 218 | @echo 219 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 220 | 221 | .PHONY: dummy 222 | dummy: 223 | $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy 224 | @echo 225 | @echo "Build finished. Dummy builder generates no files." 226 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source 10 | set I18NSPHINXOPTS=%SPHINXOPTS% source 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. epub3 to make an epub3 31 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 32 | echo. text to make text files 33 | echo. man to make manual pages 34 | echo. texinfo to make Texinfo files 35 | echo. gettext to make PO message catalogs 36 | echo. changes to make an overview over all changed/added/deprecated items 37 | echo. xml to make Docutils-native XML files 38 | echo. pseudoxml to make pseudoxml-XML files for display purposes 39 | echo. linkcheck to check all external links for integrity 40 | echo. doctest to run all doctests embedded in the documentation if enabled 41 | echo. coverage to run coverage check of the documentation if enabled 42 | echo. dummy to check syntax errors of document sources 43 | goto end 44 | ) 45 | 46 | if "%1" == "clean" ( 47 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 48 | del /q /s %BUILDDIR%\* 49 | goto end 50 | ) 51 | 52 | 53 | REM Check if sphinx-build is available and fallback to Python version if any 54 | %SPHINXBUILD% 1>NUL 2>NUL 55 | if errorlevel 9009 goto sphinx_python 56 | goto sphinx_ok 57 | 58 | :sphinx_python 59 | 60 | set SPHINXBUILD=python -m sphinx.__init__ 61 | %SPHINXBUILD% 2> nul 62 | if errorlevel 9009 ( 63 | echo. 64 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 65 | echo.installed, then set the SPHINXBUILD environment variable to point 66 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 67 | echo.may add the Sphinx directory to PATH. 68 | echo. 69 | echo.If you don't have Sphinx installed, grab it from 70 | echo.http://sphinx-doc.org/ 71 | exit /b 1 72 | ) 73 | 74 | :sphinx_ok 75 | 76 | 77 | if "%1" == "html" ( 78 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 79 | if errorlevel 1 exit /b 1 80 | echo. 81 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 82 | goto end 83 | ) 84 | 85 | if "%1" == "dirhtml" ( 86 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 87 | if errorlevel 1 exit /b 1 88 | echo. 89 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 90 | goto end 91 | ) 92 | 93 | if "%1" == "singlehtml" ( 94 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 95 | if errorlevel 1 exit /b 1 96 | echo. 97 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 98 | goto end 99 | ) 100 | 101 | if "%1" == "pickle" ( 102 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 103 | if errorlevel 1 exit /b 1 104 | echo. 105 | echo.Build finished; now you can process the pickle files. 106 | goto end 107 | ) 108 | 109 | if "%1" == "json" ( 110 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 111 | if errorlevel 1 exit /b 1 112 | echo. 113 | echo.Build finished; now you can process the JSON files. 114 | goto end 115 | ) 116 | 117 | if "%1" == "htmlhelp" ( 118 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 119 | if errorlevel 1 exit /b 1 120 | echo. 121 | echo.Build finished; now you can run HTML Help Workshop with the ^ 122 | .hhp project file in %BUILDDIR%/htmlhelp. 123 | goto end 124 | ) 125 | 126 | if "%1" == "qthelp" ( 127 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 128 | if errorlevel 1 exit /b 1 129 | echo. 130 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 131 | .qhcp project file in %BUILDDIR%/qthelp, like this: 132 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\pypref.qhcp 133 | echo.To view the help file: 134 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\pypref.ghc 135 | goto end 136 | ) 137 | 138 | if "%1" == "devhelp" ( 139 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 140 | if errorlevel 1 exit /b 1 141 | echo. 142 | echo.Build finished. 143 | goto end 144 | ) 145 | 146 | if "%1" == "epub" ( 147 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 148 | if errorlevel 1 exit /b 1 149 | echo. 150 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 151 | goto end 152 | ) 153 | 154 | if "%1" == "epub3" ( 155 | %SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3 156 | if errorlevel 1 exit /b 1 157 | echo. 158 | echo.Build finished. The epub3 file is in %BUILDDIR%/epub3. 159 | goto end 160 | ) 161 | 162 | if "%1" == "latex" ( 163 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 164 | if errorlevel 1 exit /b 1 165 | echo. 166 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 167 | goto end 168 | ) 169 | 170 | if "%1" == "latexpdf" ( 171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 172 | cd %BUILDDIR%/latex 173 | make all-pdf 174 | cd %~dp0 175 | echo. 176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 177 | goto end 178 | ) 179 | 180 | if "%1" == "latexpdfja" ( 181 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 182 | cd %BUILDDIR%/latex 183 | make all-pdf-ja 184 | cd %~dp0 185 | echo. 186 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 187 | goto end 188 | ) 189 | 190 | if "%1" == "text" ( 191 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 192 | if errorlevel 1 exit /b 1 193 | echo. 194 | echo.Build finished. The text files are in %BUILDDIR%/text. 195 | goto end 196 | ) 197 | 198 | if "%1" == "man" ( 199 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 200 | if errorlevel 1 exit /b 1 201 | echo. 202 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 203 | goto end 204 | ) 205 | 206 | if "%1" == "texinfo" ( 207 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 208 | if errorlevel 1 exit /b 1 209 | echo. 210 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 211 | goto end 212 | ) 213 | 214 | if "%1" == "gettext" ( 215 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 216 | if errorlevel 1 exit /b 1 217 | echo. 218 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 219 | goto end 220 | ) 221 | 222 | if "%1" == "changes" ( 223 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 224 | if errorlevel 1 exit /b 1 225 | echo. 226 | echo.The overview file is in %BUILDDIR%/changes. 227 | goto end 228 | ) 229 | 230 | if "%1" == "linkcheck" ( 231 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 232 | if errorlevel 1 exit /b 1 233 | echo. 234 | echo.Link check complete; look for any errors in the above output ^ 235 | or in %BUILDDIR%/linkcheck/output.txt. 236 | goto end 237 | ) 238 | 239 | if "%1" == "doctest" ( 240 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 241 | if errorlevel 1 exit /b 1 242 | echo. 243 | echo.Testing of doctests in the sources finished, look at the ^ 244 | results in %BUILDDIR%/doctest/output.txt. 245 | goto end 246 | ) 247 | 248 | if "%1" == "coverage" ( 249 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage 250 | if errorlevel 1 exit /b 1 251 | echo. 252 | echo.Testing of coverage in the sources finished, look at the ^ 253 | results in %BUILDDIR%/coverage/python.txt. 254 | goto end 255 | ) 256 | 257 | if "%1" == "xml" ( 258 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 259 | if errorlevel 1 exit /b 1 260 | echo. 261 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 262 | goto end 263 | ) 264 | 265 | if "%1" == "pseudoxml" ( 266 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 267 | if errorlevel 1 exit /b 1 268 | echo. 269 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 270 | goto end 271 | ) 272 | 273 | if "%1" == "dummy" ( 274 | %SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy 275 | if errorlevel 1 exit /b 1 276 | echo. 277 | echo.Build finished. Dummy builder generates no files. 278 | goto end 279 | ) 280 | 281 | :end 282 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # pysimplelog documentation build configuration file, created by 4 | # sphinx-quickstart on Mon Sep 12 22:54:29 2016. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | # If extensions (or modules to document with autodoc) are in another directory, 16 | # add these directories to sys.path here. If the directory is relative to the 17 | # documentation root, use os.path.abspath to make it absolute, like shown here. 18 | # 19 | #import os 20 | #import sys 21 | # sys.path.insert(0, os.path.abspath('.')) 22 | 23 | # -- General configuration ------------------------------------------------ 24 | 25 | # If your documentation needs a minimal Sphinx version, state it here. 26 | # 27 | # needs_sphinx = '1.0' 28 | 29 | # Add any Sphinx extension module names here, as strings. They can be 30 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 31 | # ones. 32 | extensions = [ 33 | 'sphinx.ext.autodoc', 34 | 'sphinx.ext.imgmath', 35 | 'sphinx.ext.githubpages', 36 | ] 37 | 38 | # Add any paths that contain templates here, relative to this directory. 39 | templates_path = ['_templates'] 40 | 41 | # The suffix(es) of source filenames. 42 | # You can specify multiple suffix as a list of string: 43 | # 44 | # source_suffix = ['.rst', '.md'] 45 | source_suffix = '.rst' 46 | 47 | # The encoding of source files. 48 | # 49 | # source_encoding = 'utf-8-sig' 50 | 51 | # The master toctree document. 52 | master_doc = 'index' 53 | 54 | # General information about the project. 55 | project = u'pysimplelog' 56 | copyright = u'2016, Bachir Aoun' 57 | author = u'Bachir Aoun' 58 | 59 | # The version info for the project you're documenting, acts as replacement for 60 | # |version| and |release|, also used in various other places throughout the 61 | # built documents. 62 | # 63 | # The short X.Y version. 64 | try: 65 | # get pysimplelog version 66 | from pysimplelog import __version__ as VER 67 | from pysimplelog import SimpleLog 68 | SimpleLog.__doc__ = SimpleLog.__doc__.replace("%AUTO_VERSION", VER) 69 | except: 70 | VER = "unknown" 71 | version = VER 72 | # The full version, including alpha/beta/rc tags. 73 | release = VER 74 | 75 | rst_epilog = """ 76 | .. |VERSION| replace:: %s 77 | """%(VER,) 78 | 79 | # The language for content autogenerated by Sphinx. Refer to documentation 80 | # for a list of supported languages. 81 | # 82 | # This is also used if you do content translation via gettext catalogs. 83 | # Usually you set "language" from the command line for these cases. 84 | language = None 85 | 86 | # There are two options for replacing |today|: either, you set today to some 87 | # non-false value, then it is used: 88 | # 89 | # today = '' 90 | # 91 | # Else, today_fmt is used as the format for a strftime call. 92 | # 93 | # today_fmt = '%B %d, %Y' 94 | 95 | # List of patterns, relative to source directory, that match files and 96 | # directories to ignore when looking for source files. 97 | # This patterns also effect to html_static_path and html_extra_path 98 | exclude_patterns = [] 99 | 100 | # The reST default role (used for this markup: `text`) to use for all 101 | # documents. 102 | # 103 | # default_role = None 104 | 105 | # If true, '()' will be appended to :func: etc. cross-reference text. 106 | # 107 | # add_function_parentheses = True 108 | 109 | # If true, the current module name will be prepended to all description 110 | # unit titles (such as .. function::). 111 | # 112 | # add_module_names = True 113 | 114 | # If true, sectionauthor and moduleauthor directives will be shown in the 115 | # output. They are ignored by default. 116 | # 117 | # show_authors = False 118 | 119 | # The name of the Pygments (syntax highlighting) style to use. 120 | pygments_style = 'sphinx' 121 | 122 | # A list of ignored prefixes for module index sorting. 123 | # modindex_common_prefix = [] 124 | 125 | # If true, keep warnings as "system message" paragraphs in the built documents. 126 | # keep_warnings = False 127 | 128 | # If true, `todo` and `todoList` produce output, else they produce nothing. 129 | todo_include_todos = False 130 | 131 | # set autodoc order 132 | autodoc_member_order = 'bysource' 133 | # -- Options for HTML output ---------------------------------------------- 134 | 135 | # The theme to use for HTML and HTML Help pages. See the documentation for 136 | # a list of builtin themes. 137 | # 138 | html_theme = 'alabaster' 139 | 140 | # Theme options are theme-specific and customize the look and feel of a theme 141 | # further. For a list of options available for each theme, see the 142 | # documentation. 143 | # 144 | #html_theme_options = {} 145 | html_theme_options = { 146 | 'github_user': "bachiraoun", 147 | 'github_repo': "pysimplelog", 148 | 'github_banner': True, 149 | 'show_powered_by':False, 150 | } 151 | 152 | 153 | 154 | # Add any paths that contain custom themes here, relative to this directory. 155 | # html_theme_path = [] 156 | 157 | # The name for this set of Sphinx documents. 158 | # " v documentation" by default. 159 | # 160 | html_title = u'PYthon SIMPLE LOGger package. pysimplelog v%s'%VER 161 | html_short_title = html_title 162 | project_name = html_title 163 | 164 | # A shorter title for the navigation bar. Default is the same as html_title. 165 | # 166 | # html_short_title = None 167 | 168 | # The name of an image file (relative to this directory) to place at the top 169 | # of the sidebar. 170 | # 171 | # html_logo = None 172 | 173 | # The name of an image file (relative to this directory) to use as a favicon of 174 | # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 175 | # pixels large. 176 | # 177 | # html_favicon = None 178 | 179 | # Add any paths that contain custom static files (such as style sheets) here, 180 | # relative to this directory. They are copied after the builtin static files, 181 | # so a file named "default.css" will overwrite the builtin "default.css". 182 | html_static_path = ['_static'] 183 | 184 | # Add any extra paths that contain custom files (such as robots.txt or 185 | # .htaccess) here, relative to this directory. These files are copied 186 | # directly to the root of the documentation. 187 | # 188 | # html_extra_path = [] 189 | 190 | # If not None, a 'Last updated on:' timestamp is inserted at every page 191 | # bottom, using the given strftime format. 192 | # The empty string is equivalent to '%b %d, %Y'. 193 | # 194 | # html_last_updated_fmt = None 195 | 196 | # If true, SmartyPants will be used to convert quotes and dashes to 197 | # typographically correct entities. 198 | # 199 | # html_use_smartypants = True 200 | 201 | # Custom sidebar templates, maps document names to template names. 202 | # 203 | html_sidebars = { 204 | '**': [], 205 | 'using/windows': [], 206 | } 207 | 208 | 209 | # Additional templates that should be rendered to pages, maps page names to 210 | # template names. 211 | # 212 | # html_additional_pages = {} 213 | 214 | # If false, no module index is generated. 215 | # 216 | # html_domain_indices = True 217 | 218 | # If false, no index is generated. 219 | # 220 | # html_use_index = True 221 | 222 | # If true, the index is split into individual pages for each letter. 223 | # 224 | # html_split_index = False 225 | 226 | # If true, links to the reST sources are added to the pages. 227 | # 228 | html_show_sourcelink = False 229 | 230 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 231 | # 232 | html_show_sphinx = False 233 | 234 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 235 | # 236 | html_show_copyright = True 237 | 238 | # If true, an OpenSearch description file will be output, and all pages will 239 | # contain a tag referring to it. The value of this option must be the 240 | # base URL from which the finished HTML is served. 241 | # 242 | # html_use_opensearch = '' 243 | 244 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 245 | # html_file_suffix = None 246 | 247 | # Language to be used for generating the HTML full-text search index. 248 | # Sphinx supports the following languages: 249 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' 250 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh' 251 | # 252 | # html_search_language = 'en' 253 | 254 | # A dictionary with options for the search language support, empty by default. 255 | # 'ja' uses this config value. 256 | # 'zh' user can custom change `jieba` dictionary path. 257 | # 258 | # html_search_options = {'type': 'default'} 259 | 260 | # The name of a javascript file (relative to the configuration directory) that 261 | # implements a search results scorer. If empty, the default will be used. 262 | # 263 | # html_search_scorer = 'scorer.js' 264 | 265 | # Output file base name for HTML help builder. 266 | htmlhelp_basename = 'pysimplelogdoc' 267 | 268 | # -- Options for LaTeX output --------------------------------------------- 269 | 270 | latex_elements = { 271 | # The paper size ('letterpaper' or 'a4paper'). 272 | # 273 | # 'papersize': 'letterpaper', 274 | 275 | # The font size ('10pt', '11pt' or '12pt'). 276 | # 277 | # 'pointsize': '10pt', 278 | 279 | # Additional stuff for the LaTeX preamble. 280 | # 281 | # 'preamble': '', 282 | 283 | # Latex figure (float) alignment 284 | # 285 | # 'figure_align': 'htbp', 286 | } 287 | 288 | # Grouping the document tree into LaTeX files. List of tuples 289 | # (source start file, target name, title, 290 | # author, documentclass [howto, manual, or own class]). 291 | latex_documents = [ 292 | (master_doc, 'pysimplelog.tex', u'pysimplelog Documentation', 293 | u'Bachir Aoun', 'manual'), 294 | ] 295 | 296 | # The name of an image file (relative to this directory) to place at the top of 297 | # the title page. 298 | # 299 | # latex_logo = None 300 | 301 | # For "manual" documents, if this is true, then toplevel headings are parts, 302 | # not chapters. 303 | # 304 | # latex_use_parts = False 305 | 306 | # If true, show page references after internal links. 307 | # 308 | # latex_show_pagerefs = False 309 | 310 | # If true, show URL addresses after external links. 311 | # 312 | # latex_show_urls = False 313 | 314 | # Documents to append as an appendix to all manuals. 315 | # 316 | # latex_appendices = [] 317 | 318 | # It false, will not define \strong, \code, itleref, \crossref ... but only 319 | # \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added 320 | # packages. 321 | # 322 | # latex_keep_old_macro_names = True 323 | 324 | # If false, no module index is generated. 325 | # 326 | # latex_domain_indices = True 327 | 328 | 329 | # -- Options for manual page output --------------------------------------- 330 | 331 | # One entry per manual page. List of tuples 332 | # (source start file, name, description, authors, manual section). 333 | man_pages = [ 334 | (master_doc, 'pysimplelog', u'pysimplelog Documentation', 335 | [author], 1) 336 | ] 337 | 338 | # If true, show URL addresses after external links. 339 | # 340 | # man_show_urls = False 341 | 342 | 343 | # -- Options for Texinfo output ------------------------------------------- 344 | 345 | # Grouping the document tree into Texinfo files. List of tuples 346 | # (source start file, target name, title, author, 347 | # dir menu entry, description, category) 348 | texinfo_documents = [ 349 | (master_doc, 'pysimplelog', u'pysimplelog Documentation', 350 | author, 'pysimplelog', 'One line description of project.', 351 | 'Miscellaneous'), 352 | ] 353 | 354 | # Documents to append as an appendix to all manuals. 355 | # 356 | # texinfo_appendices = [] 357 | 358 | # If false, no module index is generated. 359 | # 360 | # texinfo_domain_indices = True 361 | 362 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 363 | # 364 | # texinfo_show_urls = 'footnote' 365 | 366 | # If true, do not generate a @detailmenu in the "Top" node's menu. 367 | # 368 | # texinfo_no_detailmenu = False 369 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to pysimplelog V. |VERSION| documentation! 2 | ================================================== 3 | 4 | .. automodule:: pysimplelog 5 | :members: 6 | 7 | 8 | .. automodule:: pysimplelog.SimpleLog 9 | :members: 10 | :undoc-members: 11 | :noindex: 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """ 2 | This script will work from within the main package directory. 3 | 4 | python setup.py sdist bdist_wheel 5 | twine upload dist/pysimplelog-... 6 | 7 | 8 | """ 9 | try: 10 | from setuptools import setup 11 | except: 12 | from distutils.core import setup 13 | from distutils.util import convert_path 14 | import os, sys 15 | 16 | # set package path and name 17 | PACKAGE_PATH = '.' 18 | PACKAGE_NAME = 'pysimplelog' 19 | 20 | # check python version 21 | if sys.version_info[:2] < (2, 6): 22 | raise RuntimeError("Python version 2.6.x and above is required.") 23 | 24 | # automatically create MANIFEST.in 25 | commands = [# include MANIFEST.in 26 | '# include this file, to ensure we can recreate source distributions', 27 | 'include MANIFEST.in' 28 | # exclude all .log files 29 | '\n# exclude all logs', 30 | 'global-exclude *.log', 31 | # exclude all other non necessary files 32 | '\n# exclude all other non necessary files ', 33 | 'global-exclude .project', 34 | 'global-exclude .pydevproject', 35 | # exclude all of the subversion metadata 36 | '\n# exclude all of the subversion metadata', 37 | 'global-exclude *.svn*', 38 | 'global-exclude .svn/*', 39 | 'global-exclude *.git*', 40 | 'global-exclude .git/*', 41 | # include all LICENCE files 42 | '\n# include all license files found', 43 | 'global-include ./*LICENSE.*', 44 | # include all README files 45 | '\n# include all readme files found', 46 | 'global-include ./*README.*', 47 | 'global-include ./*readme.*'] 48 | with open('MANIFEST.in','w') as fd: 49 | for l in commands: 50 | fd.write(l) 51 | fd.write('\n') 52 | 53 | # declare classifiers 54 | CLASSIFIERS = """\ 55 | Development Status :: 5 - Production/Stable 56 | Intended Audience :: Science/Research 57 | Intended Audience :: Developers 58 | License :: OSI Approved :: GNU Affero General Public License v3 59 | Programming Language :: Python 60 | Programming Language :: Python :: 2.7 61 | Programming Language :: Python :: 3 62 | Topic :: Software Development 63 | Topic :: Software Development :: Build Tools 64 | Topic :: Scientific/Engineering 65 | Operating System :: Microsoft :: Windows 66 | Operating System :: POSIX 67 | Operating System :: Unix 68 | Operating System :: MacOS 69 | """ 70 | 71 | # create descriptions 72 | LONG_DESCRIPTION = ["This is a pythonic simple yet complete system logger.", 73 | "It allows logging simultaneously to two streams, the first one is the system standard output by default and the second one is designated to be set to a file.", 74 | "In addition, pysimplelog is text colouring and attributes enabled when the stream allows it.",] 75 | DESCRIPTION = [ LONG_DESCRIPTION[0] ] 76 | 77 | ## get package info 78 | PACKAGE_INFO={} 79 | infoPath = convert_path('__pkginfo__.py') 80 | with open(infoPath) as fd: 81 | exec(fd.read(), PACKAGE_INFO) 82 | 83 | 84 | # create meta data 85 | metadata = dict(name = PACKAGE_NAME, 86 | packages=[PACKAGE_NAME], 87 | package_dir={PACKAGE_NAME: '.'}, 88 | version= PACKAGE_INFO['__version__'] , 89 | author="Bachir AOUN", 90 | author_email="bachir.aoun@e-aoun.com", 91 | description = "\n".join(DESCRIPTION), 92 | long_description = "\n".join(LONG_DESCRIPTION), 93 | url = "http://bachiraoun.github.io/pysimplelog/", 94 | download_url = "https://github.com/bachiraoun/simplelog", 95 | license = 'GNU', 96 | classifiers=[_f for _f in CLASSIFIERS.split('\n') if _f], 97 | platforms = ["Windows", "Linux", "Mac OS-X", "Unix"], ) 98 | 99 | # setup 100 | setup(**metadata) 101 | --------------------------------------------------------------------------------