Package: diary
DiaryDiary is a lightweight, no-dependency, asynchronous logging module. Diary has an
easy to use API for the simple and extensive use case.Table of ContentsDiaryFeaturesInstallationSupportSimple APIQuick UseCustomizationDocumentationDiaryEventDiaryDBDiaryThreadformatslevelslog_levelDiary Command LineContributingLicenseFeaturesAll logging processes are asynchronous to not disrupt the flow of the programNo dependenciesWrite to a database, text file, or bothUse custom:FormattingDatabase configurationsLogging levelsInstallationFrom pypi:$ pip install diaryOr:$ easy_install diaryOr clone from github:$ git clone https://github.com/SamGRosen/diary.git
$ cd diary
$ sudo python setup.py installSupportPlease feel free to make issues on thegithub repo.Pull requests are more than welcome.Simple APIQuick UseImport the main object:from diary import Diary
logger = Diary("log.txt")
logger.log("Started app")
number = 10
logger.log("Initialized numbers")Use different logging levels:from diary import levels
# Diary.log defaults to the info level but can be specified as a keyword argument
logger.log("Starting levels demonstration", level=levels.debug)
logger.info("This is the default log level")
logger.warn("This seems fishy")
logger.error("Contact admin")
logger.debug("Failed import; using alternate")CustomizationDefining a custom level:from diary import log_level

@log_level
def critical(event):
    event.info = "!! " + event.info + " !!"

logger = Diary("EmergencyLogs.log")
logger.log("URGENT ATTENTION NEEDED", level=critical)
with open(logger.log_file.name) as f:
    print(f.readline())  # [CRITICAL]:[2016-08-15 05:12:27.566642]: !! URGENT ATTENTION NEEDED !!Defining a custom formatter:def emergency_format(event):
    return "!!!|{dt}|{level}|{info}|!!!".format(
        dt=event.dt, level=event.level_text, info=event.info
    )
logger = Diary("EmergencyLogs2.log", log_format=emergency_format)

# OR We can define a custom event with a formatter
from diary import Event

class EmergencyEvent(Event):
    formatter = "|{dt}|{level_str}|{info}|"

logger = Diary("EmergencyLogs3.log", event=EmergencyEvent)
logger.log("There is a catastrophic issue")Using a custom event:class UserEvent(Event):
    formatter = "[{level_str}]|{dt}|{info}|{user_name}"

    def __init__(self, info, level=None, user_name=""):
          Event.__init__(self, info, level)
          self.user_name = user_name

logger = Diary("UserEvents.txt", event=UserEvent)
logger.log("Start logging")
logger.info(UserEvent("admin logged in", user_name="admin"))  # Directly log events
logger.warn(UserEvent("Unknown user logged in", user_name="127.0.0.1"))Using a custom database:from diary import DiaryDB
class UserActivityDB(DiaryDB):
    def create_tables(self):
         self.cursor.execute('''CREATE TABLE IF NOT EXISTS user_activity
                                (inputDT TIMESTAMP, level TEXT, log TEXT, user TEXT)''')
    def log(self, event):
        with self.conn:
            self.cursor.execute('''
                                INSERT INTO user_activity(inputDT, level, log, user)
                                                 VALUES(?, ?, ?, ?)''',
                                (event.dt, event.level_str, event.info, event.user_name))

logger = Diary("UserActivity.txt", event=UserEvent, db=UserActivityDB)
logger.log("Starting app")
logger.debug(UserEvent("Super user logged in", user_name="super"))
logger.log(UserEvent("Hacker logged in", user_name="badguy"), level=critical)
logger.close()
with UserActivityDB(logger.db_file.name) as db:
    db.cursor.execute("SELECT * FROM user_activity")DocumentationDiaryDiary is highest level part of the API; it is where all events are logged and processed.Initializationclass Diary(path,file_name="diary.txt",db_name="diary.db",event=events.Event,log_format=formats.standard, db=logdb.DiaryDB, async=True, debug_enabled=True)pathstrof a path pointing to:An empty directory where Diary will create a db and logA text file where Diary will appendA database file where Diary will read and writeA directory with a database and txt filelooks for file_name and db_name argumentsA nonexistent path where a db or log will be madefile_namestrname for diary to look for during initialization or name of log file to be madedb_namestrname for diary to look for during initialization or name of db fileeventEventEvent class which will initialize with logged stringslog_formatfunctionwhich takes an event parameter and outputs a formatted stringdbDiaryDBDatabase type to be constructed for loggingasyncboolWhether or not Diary should run in async modedebug_enabledboolWhether or not Diary should allow debug log levelencodingstrtype of encoding for log file to usealso_printboolif logged events should also print to stdoutFields(Not listed above)db_fileFilewhere database is storedlast_logged_eventEventlast event that was loggedlog_fileFilewhere log file is storedlogdbDiaryDBset during set_db; DiaryDB instance that is stored tothreadDiaryThreadif run in async mode, the thread that is handling loggingtimerRepeatedTimerset during set_timer; thread to repeat a functionUseful for logging information every interval (such as app status)Methodsclose()Close the resources used (automatically called on exit)debug(info, **kwargs)Log info with the debug level, kwargs passed to levels.debugerror(info, **kwargs)Log info with the error level, kwargs passed to levels.errorinfo(info, **kwargs)Log info with the info level, kwargs passed to levels.infolog(info, level=levels.info, **kwargs)Log info with the specified level, kwargs passed to levelset_db()To keep a db thread safe this is called by the DiaryThread or in the constructor if async is Falseset_timer(func, interval, *args, **kwargs)Set a func to be called every interval with given parameterswarn(info, **kwargs)Log info with the warn level, kwargs passed to levels.warnwrite(event)Write an event to log_file, db_file, or bothEventEvent describes information that is logged and is easily customized by inheritance.Initializationclass Event(info, level=None, dt=None)infoInformation to loglevelthe level that this event falls underdtdatetimethe time this event occurs (automatically set if dt is None)Fields(Not listed above)formatterclass variable of formatting method either a string or functionlevel_strstrthe level as a readable stringMethodsformatted()returns the event in a readable fashion for loggingEvent.set_formatter(formatter)set the class to formatterset_level(level)set levelInheritingEvent subclasses should set class level variables for formatterformatter should be a str which follows str.format syntax and kwarg fields should not contain selfGOOD:formatter ="|{info}|{level_str}|{dt}|"BAD:formatter ="|{self.info}|{self.level}|{self.dt}|"If an Event subclass has extra fields a DiaryDB subclass will have to be made to put those extra fields in a dbDiaryDBDiaryDB is used to log Events into a database. DiaryDB uses SQLite3 but this can be changed by creating your own DiaryDB subclass.DiaryDB can be used in a context manager.withDiaryDB("path/to/file")as db:Initializationclass DiaryDB(path)pathstrpath of database to use. If no path is passed and the python command was invoked normally, it will look for a file named ‘log.sqlite3’ in the root folder of your application. In some edge cases when python programs aren’t invoked through the process interface, you will need to pass a custom path even if ‘log.sqlite3’ is in the root folder of the application.Fields(Not listed above)connsqlite3.connectionConnection to databasecursorsqlite3.cursorCursor for execution to connectionMethodsassert_event_logged(log,level='%',limit=-1)Assert that an event matching the given parameters existsclose()Close the database connectioncreate_tables()Called on construction, creates tables in database for uselog(event)Log an event into the database, automatically commits executions.InheritingIf an Event subclass with extra attributes is logged only its datetime, info, and level are put into the databaseTo Create a DiaryDB capable of handling specific Event subclasses...override the create_tables method to create a table with a column for each attributeoverride the log method to execute the event attributes into your created tablesIf you would like to use Diary to validate tests it is recommended you override assert_event_logged to accommodate specific events.Using different configurationsTo use a different database configurations simple inherit DiaryDB and
override __init__, create_tables, log, and close.DiaryThreadDiaryThread is used by Diary to complete all logging processes asynchronously.
It has very little source code and is easily understood.
However inheriting from DiaryThread is not recommended and can only be utilized in a Diary subclass.Initializationclass DiaryThread(diary, sets_db=False,name="DiaryLogger")diaryDiarydiary to complete loggingsets_dbboolif database is set in run methodnamestridentifier of threadFields(Not listed above or inherited)queueQueueevents waiting to be loggedMethodsadd(event)queue an event for loggingjoin([timeout])Process all events in queue and stop threadrun()Main worker for DiaryThreadformatsFormats are predefined functions that can be passed into Diary __init__ to give logs a more appropriate format.All formats only support name, info, and dt attributes of events.
Write a custom format for your custom events, however it is recommended to give your Event subclasses a formatter field.alarmsSeparate event attributes with ! if an event has an error level!!ERROR!!2016-07-31 21:55:00.165649!!NOOOOO!!easy_readAn easy to read format|INFO| On 07/30/16 @ 08:18.55PM | example textminimalA minimal formatINFO: 07/30/16 20:15:48: example textstandardA standard format[INFO]:[2016-07-30 20:18:09.401149]: example textstringify_info(info)return info as a readable stringstringify_level(level)return level as a readable stringlevelsLevels are used to appropriately handle events based on their significance.
Developers are encouraged to define their own levels as the provided levels either have no side effects or have limited extra use.log_levelLevels should be functions decorated by@log_levelto ensure they are reported correctly.
Level calls should look likelevel(event, reporter, **kwargs); this allows an event to be reported and handled based on the kwargs.
Keyword arguments are always passed into the decorated level function.debug(event)Info only pertinent to developers, no side effects.error(event, raises=False, e_type=Exception, log_trace=True, limit=None)Errors in the program executionraisesboolStops the program if an error is loggede_typeExceptiontype of exception to be raisedlog_traceboolAdd to event.info the stacktrace leading up to errorlimitintDepth of stacktraceinfo(event)General info, no side effectswarn(event, log_trace=False)Warnings for potential issueslog_traceboolAdd to event.info the stacktrace leading up to the warningDiary Command LineDiary comes with a command line tool,diary, which can be used to generate a SQLite3 database for your diary application. Running the command is simplediary generate sqlite [path]This will generate a SQLite3 database for diary at[path]. The default path islog.sqlite3. You should run this command in either the root directory of your project or within a logs folder for your project. If it is ran in the root directory and you use DiaryDB, diary will automatically know where to put your logs.ContributingGetting StartedRight now, diary is looking for contributors to help create formats, levels, and different database configurations. To begin contributing:Fork or clone the repositorygit clone https://github.com/SamGRosen/diary.gitRead the source and setup a virtual environmentvirtualenv venv
source venv/bin/activateRun the unit testspython tests/run_tests.pyImplement your changes and write unit tests for them.Submit a pull request.LicenseDiary is protected by the MIT license