# copyright (C) 1997-2006 Jean-Luc Fontaine (mailto:jfontain@free.fr)
# this program is free software: please read the COPYRIGHT file enclosed in this package or use the Help Copyright menu

# $Id: myerrorlog.tcl,v 1.50 2006/03/16 21:29:56 jfontain Exp $


package provide myerrorlog [lindex {$Revision: 1.50 $} 1]
package require miscellaneous 1
package require network 1


namespace eval myerrorlog {

    variable numberOfRows 10                                                                                           ;# by default
    variable dateFormat {%d %b %Y}
    variable timeFormat %T
    variable moduleDirectory [pwd]                                                                      ;# remember for message file
    variable unimportantSignal
    array set unimportantSignal {1 SIGHUP 2 SIGINT 3 SIGQUIT 6 SIGABRT 9 SIGKILL 15 SIGTERM}
    variable encoding                                          ;# MySQL international message files language to Tcl encoding mapping
    array set encoding {
        czech iso8859-2 danish iso8859-1 dutch iso8859-1 english iso8859-1 estonian cp1257 french iso8859-1 german iso8859-1
        greek iso8859-7 hungarian iso8859-2 italian iso8859-1 japanese euc-jp korean euc-kr norwegian-ny iso8859-1
        norwegian iso8859-1 polish iso8859-2 portuguese iso8859-1 romanian iso8859-2 russian koi8-r slovak iso8859-2
        spanish iso8859-1 swedish iso8859-1 ukrainian koi8-r
    }

    array set data {
        updates 0
        0,label {} 0,type integer 0,message {row index (0 is always the 'top' type row)}
        1,label type 1,type ascii 1,message {message type: 'new' for messages that occured during the last poll, 'old' for messages that occured before the last poll, 'top' being for the most important message among the 'new'}
        2,label level 2,type integer 2,message {importance level of the message, the higher the value the more important the message, according to the following standard classification: 0 (emergency), 1 (alert), 2 (critical), 3 (error), 4 (warning), 5 (notice), 6 (info), 7 (debug)}
        3,label date 3,type clock 3,message {date of latest message occurrence of its sort}
        4,label time 4,type clock 4,message {time of latest message occurrence of its sort}
        5,label occurences 5,type integer 5,message {number of occurrences of this sort of message since this module was launched}
        6,label message 6,type ascii 6,message {text of the latest message of its sort (may be empty for the 'top' type row if there were no 'new' messages)} 6,anchor left
        7,label index 7,type integer 7,message {message internal index}
        views {{indices {1 2 3 4 5 6}}}
        persistent 0
        switches {--debug-file 1 --dsn 1 -f 1 --file 1 --password 1 --port 1 -r 1 --remote 1 --rows 1 --user 1 --whole 0}
    }
    set file [open myerrorlog.htm]
    set data(helpText) [read $file]                                                           ;# initialize HTML help data from file
    close $file

    proc initialize {optionsName} {
        upvar 1 $optionsName options
        variable remote
        variable data
        variable numberOfRows
        variable errorLog
        variable hardCodedMessages
        variable moduleDirectory
        variable encoding

        if {[info exists options(--debug-file)]} {
            variable debugFile [open $options(--debug-file) w]
            puts $debugFile "module myerrorlog version: [package present myerrorlog]"
        }
        catch {set numberOfRows $options(--rows)}
        catch {set errorLog(file) $options(-f)}
        catch {set errorLog(file) $options(--file)}                                                             ;# favor long option

        if {$numberOfRows <= 0} {set numberOfRows 2147483647}                                            ;# eventually no limitation
        if {![catch {set locator $options(--remote)}] || ![catch {set locator $options(-r)}]} {                 ;# remote monitoring
            set data(pollTimes) {30 20 60 120 300 600}                                   ;# poll less often when remotely monitoring
            foreach {remote(protocol) remote(user) remote(host)} [network::parseRemoteLocator $locator] {}
            if {![info exists errorLog(file)]} {
                network::checkRemoteOutputEmptiness $remote(protocol) $remote(user) $remote(host)
            }
            set data(identifier) myerrorlog($remote(host))
        } else {
            set data(pollTimes) {20 10 30 60 120 300 600}
        }
        set user $::tcl_platform(user)                                                                                 ;# by default
        catch {set user $options(--user)}
        if {[info exists options(--dsn)]} {                                               ;# ODBC mode id data source name specified
            package require tclodbc 2                               ;# so that it works with both UNIX 2.2.1 et Windows 2.3 versions
            if {[info exists options(--port)]} {
                error {--port option incompatible with ODBC mode}
            }
            set arguments [list $options(--dsn)]
            catch {lappend arguments $user}
            catch {lappend arguments $options(--password)}
            set connection [eval database odbc $arguments]                                           ;# use a unique connection name
            set data(identifier) myerrorlog($options(--dsn))
            $connection statement $connection.query {show variables}
            $connection.query execute
            while {[llength [set list [$connection.query fetch]]] > 0} {
                foreach {variable value} $list {}
                switch $variable {
                    datadir {set directory $value}
                    language {set language $value}
                }
            }
            $connection disconnect
        } else {
            package require mysqltcl
            if {[info exists options(--port)] && (![info exists remote(host)] || [string equal $remote(host) localhost])} {
                error {--port option useless with local socket connection (localhost as host)}
            }
            set arguments {}
            catch {lappend arguments -host $remote(host)}
            catch {lappend arguments -user $user}
            catch {lappend arguments -password $options(--password)}
            catch {lappend arguments -port $options(--port)}
            set connection [eval mysqlconnect $arguments]
            set host [lindex [mysqlinfo $connection host] 0]               ;# work around mysqltcl 3 return value: "host via TCP/IP"
            set data(identifier) myerrorlog($host)
            mysqlsel $connection {show variables}
            while {[llength [set list [mysqlnext $connection]]] > 0} {
                foreach {variable value} $list {}
                switch $variable {
                    datadir {set directory $value}
                    language {set language $value}
                }
            }
            mysqlclose $connection
        }
        set language [file tail $language]                               ;# for example, gives english for /usr/share/mysql/english/
        set errorLog(encoding) $encoding($language)

        set file [open [file join $moduleDirectory $language.txt]]
        fconfigure $file -encoding $errorLog(encoding)
        set lines {}
        while {[gets $file line] >= 0} {
            if {![string match {"*"} $line] && ![string match {"*",} $line]} continue            ;# message format is: "message...",
            lappend lines [string trim [string trimright $line ,] \"]                       ;# remove surrounding " and , characters
        }
        close $file
        processErrorMessages $hardCodedMessages 0 $lines 1000                ;# 1000 = MySQL international error messages first code

        if {[info exists remote] && ![info exists errorLog(file)]} {
            set pattern [file join $directory *.err]                                                           ;# matches error logs
            if {[string equal $::tcl_platform(platform) unix]} {
                set remote(command) "$remote(protocol) -n -l $remote(user) $remote(host) ls -lt $pattern"
            } else {                                                                                                      ;# windows
                set remote(command) "plink -batch $remote(host) ls -lt $pattern"                   ;# host is rather a putty session
            }
            set file [open "| $remote(command)"]
            fileevent $file readable {set ::myerrorlog::remote(busy) 0}
            vwait ::myerrorlog::remote(busy)
            catch {unset line}
            # detect errors early (but avoid write on pipe with no readers errors by reading whole data):
            if {[catch {gets $file line; read $file; close $file} message]} {
                error "on remote host $remote(host) as user $remote(user): $message"
            }
            if {![info exists line]} {                                                        ;# no most recent error log file entry
                error "could not find any valid .err file in $directory on remote host $remote(host)"
            }
            if {[string match *->* $line]} {                                                          ;# the file is a symbolic link
                error "log file in $directory on remote host $remote(host) is a symbolic link (unimplemented):\n$line"
            }
            set errorLog(file) [lindex $line end]                                                  ;# the most recent error log file
            if {[info exists options(--whole)]} {                                             ;# read the whole file at first update
                set errorLog(position) 0
            } else {                                                                                ;# process new lines from now on
                set errorLog(position) [lindex $line end-4]                             ;# point to the file end position (its size)
            }
        } else {
            if {![info exists errorLog(file)]} {
                set maximum 0
                foreach file [glob -directory $directory *.err] {                                              ;# matches error logs
                    if {[catch {set time [file atime $file]}]} {                                 ;# does not work on FAT filesystems
                        set errorLog(file) $file       ;# use the first file found: there is a very good chance that is the good one
                        break
                    }
                    if {$time > $maximum} {                                         ;# use the most recently accessed error log file
                        set errorLog(file) $file
                        set maximum $time
                    }
                }
            }
            set errorLog(channel) [open $errorLog(file)]                              ;# keep local file open for better performance
            # so that lines are trimmed only by 1 character by gets on windows, in order to always get the byte count right:
            fconfigure $errorLog(channel) -translation binary
            if {![info exists options(--whole)]} {
                seek $errorLog(channel) 0 end                                                       ;# process new lines from now on
            }
        }
    }

    proc update {} {
        variable remote
        variable errorLog
        variable data
        variable numberOfRows
        variable occurences
        variable new
        variable messageLine
        variable dateFormat
        variable timeFormat
        variable unimportantSignal
        variable debugFile

        if {[info exists remote]} {
            if {![info exists errorLog(channel)]} {                        ;# start data gathering process in a non blocking fashion
                if {$remote(busy)} return                                           ;# core invocation while waiting for remote data
                set remote(busy) 1
                set command "dd bs=1 skip=$errorLog(position) if=$errorLog(file)"
                if {[string equal $::tcl_platform(platform) unix]} {
                    append command " 2> /dev/null"                                              ;# suppress dd informational message
                    set file [open "| $remote(protocol) -n -l $remote(user) $remote(host) $command"]
                } else {                                                                                                  ;# windows
                    append command " 2> NUL:"                                                   ;# suppress dd informational message
                    set file [open "| plink -batch $remote(host) $command"]
                }
                # so that lines are trimmed only by 1 character by gets on windows, in order to always get the byte count right:
                fconfigure $file -translation binary
                # do not hang GUI, allow other modules updates
                fileevent $file readable "set ::myerrorlog::errorLog(channel) $file; ::myerrorlog::update"
                return                                                                                       ;# wait for remote data
            }                                                                                 ;# else continue below to process data
        }
        set seconds [clock seconds]
        foreach cell [array names data *,0] {
            set pattern [scan $cell %u],\[0-9\]*                                                       ;# all data cells for the row
            array set old [array get data $pattern]
            array unset data $pattern
        }
        set number 0                                      ;# in order of arrival, used for sorting instead of more complex timestamp
        set row 0
        while {[set length [nextEntry date time firstLine message]] > 0} {                                   ;# retrieve new entries
            # to simplify matters, it is assumed that a message is uniquely identified by its first line, but the message is
            # however, displayed in its entirety
            incr number
            if {[catch {set index $messageIndex($firstLine)}]} {                     ;# cache since finding a message is rather slow
                set index [find $firstLine]
                set messageIndex($firstLine) $index
            }
            catch {unset existing}
            # keep track of the number of occurences of the message since module started:
            if {$index >= 0} {                                      ;# recognized message: count occurences of the same message type
                if {[catch {set count [incr occurences($index)]}]} {
                    set count [set occurences($index) 1]
                }
                catch {set existing $new($index)}                                                 ;# we may just have seen this type
            } else {
                if {[catch {set count [incr occurences($firstLine)]}]} {           ;# else count occurences of the same message text
                    set count [set occurences($firstLine) 1]
                    if {[info exists debugFile]} {
                        puts $debugFile "unknown message: $message"
                    }
                }
                catch {set existing $new($firstLine)}                                             ;# we may just have seen this text
            }
            set level ?
            catch {set level $messageLine($index,level)}                                                   ;# message may be unknown
            if {[info exists existing]} {                                                                  ;# duplicated new message
                set new($existing,3) $date
                set new($existing,4) $time
                set new($existing,5) $count
                set new($existing,6) $message                                   ;# use latest message which may differ if multi-line
                set new($existing,number) $number
                continue                                                                    ;# counted above, already recorded below
            }
            if {[info exists messageLine($index,signal)]} {                   ;# special processing for messages containing a signal
                set signal 0
                foreach word [words $message] {
                    # use scan instead of looking for an isolated value since signal may be followed by a comma:
                    if {[scan $word %u value]} {set signal $value; break}   ;# consider that first found integer is the signal value
                }
                if {[info exist unimportantSignal($signal)]} {set level 6} else {set level 1}
            }
            set new($row,2) $level
            set new($row,3) $date
            set new($row,4) $time
            set new($row,5) $count
            set new($row,6) $message
            set new($row,7) $index
            set new($row,number) $number
            if {$index >= 0} {                                                                       ;# remember new message as type
                set new($index) $row
            } else {                                                                                 ;# remember new message as text
                set new($firstLine) $row
            }
            lappend rows $row
            incr row
        }
        if {[info exists remote]} {
            read $errorLog(channel)                          ;# avoid write on pipe with no readers errors by reading remaining data
            if {[catch {close $errorLog(channel)} message]} {                            ;# communication error can be detected here
                set message "myerrorlog: $message"                                    ;# insert module specific error message in log
                set firstLine [lindex [split $message \n] 0]                      ;# only first line counts for matching (see above)
                if {[catch {set count [incr occurences($firstLine)]}]} {                ;# count occurences of the same message text
                    set count [set occurences($firstLine) 1]
                }
                set new($row,2) 3                                                                             ;# communication error
                set new($row,3) [clock format $seconds -format $dateFormat]
                set new($row,4) [clock format $seconds -format $timeFormat]
                set new($row,5) $count
                set new($row,6) $message
                set new($row,7) -1
                set new($row,number) [incr number]
                set new($firstLine) $row                                     ;# useless, but here for code consistency with above...
                lappend rows $row
                flashMessage "error: $message"
            }
            unset errorLog(channel)
            set remote(busy) 0
        }
        if {[info exists rows]} {                                                                             ;# there were new rows
            if {$data(updates) != 0} {
                set row 0
                set type new
            } else {                                                        ;# first update with new rows (implies --whole was used)
                set row 1                                                                                   ;# do not update top row
                set type old                                                                                 ;# mark all rows as old
            }
            foreach from [lsort -command comparison $rows] {                    ;# display new rows up to the maximum number of rows
                set data($row,0) $row
                if {$row == 0} {set data($row,1) top} else {set data($row,1) $type}
                set data($row,2) $new($from,2)
                set data($row,3) $new($from,3)
                set data($row,4) $new($from,4)
                set data($row,5) $new($from,5)
                set data($row,6) $new($from,6)
                set data($row,7) $new($from,7)
                if {[incr row] >= $numberOfRows} break
            }
        }
        if {![info exists rows] || ![info exists data(0,0)]} {                                        ;# no new rows or first update
            set data(0,0) 0
            set data(0,1) top
            set data(0,2) ?
            set data(0,3) [clock format $seconds -format $dateFormat]
            set data(0,4) [clock format $seconds -format $timeFormat]
            set data(0,5) ?
            set data(0,6) {}
            set data(0,7) -1
            set row 1
        }
        if {$row < $numberOfRows} {                                    ;# display more rows from some of the most important old rows
            set from 0
            while {![catch {set index $old($from,7)}]} {
                set message $old($from,6)
                # skip empty top row or new messages:
                if {([string length $message] > 0) && ![info exists new($index)] && ![info exists new($message)]} {
                    set data($row,0) $row
                    set data($row,1) old
                    set data($row,2) $old($from,2)
                    set data($row,3) $old($from,3)
                    set data($row,4) $old($from,4)
                    set data($row,5) $old($from,5)
                    set data($row,6) $message
                    set data($row,7) $index
                    if {[incr row] >= $numberOfRows} break
                }
                incr from
            }
        }
        catch {unset new}
        incr data(updates)
    }

    proc valid {line dateName timeName messageName} {
        upvar 1 $dateName date $timeName time $messageName message
        variable dateFormat
        variable timeFormat

        if {![regexp {^(\S+)\s+(\S+)\s+(.+)$} $line dummy first second message]} {return 0}
        if {[catch {set date [clock format [clock scan $first] -format $dateFormat]}]} {return 0}        ;# a valid date is required
        if {[catch {set time [clock format [clock scan $second] -format $timeFormat]}]} {return 0}       ;# a valid time is required
        return 1
    }

    proc nextEntry {dateName timeName firstLineName messageName} {                             ;# returns total number of bytes read
        upvar 1 $dateName date $timeName time $firstLineName firstLine $messageName message
        variable remote
        variable errorLog
        variable nextLine                                                                                 ;# local to this procedure
        variable maximumLines 3                                             ;# maximum number of lines displayed, the rest being cut

        if {![info exists nextLine]} {
            while {[set length [gets $errorLog(channel) line]] >= 0} {
                if {[info exists remote]} {                                                              ;# keep track of bytes read
                    incr errorLog(position) $length
                    incr errorLog(position)                                                     ;# count stripped new line character
                }
                # remove any remaining end of line blanks (useful on windows):
                set line [string trimright [encoding convertfrom $errorLog(encoding) $line]]
                if {[valid $line nextLine(date) nextLine(time) nextLine(text)]} break else {unset nextLine}
            }
        }
        if {![info exists nextLine]} {
            return 0
        }

        set date $nextLine(date)
        set time $nextLine(time)
        set firstLine $nextLine(text)
        set message $nextLine(text)
        unset nextLine

        set lines 0
        set append 1
        while {[set length [gets $errorLog(channel) line]] >= 0} {                       ;# retrieve new lines up to next valid line
            if {[info exists remote]} {                                                                  ;# keep track of bytes read
                incr errorLog(position) $length
                incr errorLog(position)                                                         ;# count stripped new line character
            }
            # remove any remaining end of line blanks (useful on windows):
            set line [string trimright [encoding convertfrom $errorLog(encoding) $line]]
            if {[string length [string trim $line]] == 0} {
                set append 0                                                             ;# stop appending lines at first blank line
            } else {
                if {[valid $line nextLine(date) nextLine(time) nextLine(text)]} break else {unset nextLine}    ;# stop at next entry
            }
            if {!$append || ([incr lines] > $maximumLines)} continue                                        ;# limit number of lines
            append message \n$line
            if {$lines == $maximumLines} {append message { ...}}
        }
        set message [string trimright $message]
        return 1
    }

    proc comparison {row1 row2} {                                           ;# sort in inverse order of arrival and increasing level
        variable new

        set level1 $new($row1,2)
        set level2 $new($row2,2)
        set level1 [string map {? -1} $level1]         ;# unknown level has highest importance so it can be detected then integrated
        set level2 [string map {? -1} $level2]
        if {$level1 > $level2} {return 1}
        if {$level1 < $level2} {return -1}
        set number1 $new($row1,number)                                                                           ;# order of arrival
        set number2 $new($row2,number)
        if {$number1 > $number2} {return -1}
        if {$number1 < $number2} {return 1}
        return 0
    }

    proc processErrorMessages {args} {                                                                  ;# lines code lines code ...
        variable messageLine
        variable codeLevel

        foreach {lines startCode} $args {
            set code $startCode
            foreach line $lines {
                set words [words $line]
                set level ?
                catch {set level $codeLevel($code)}
                lappend values [list [llength $words] $level $words]
#               lappend values [list [llength $words] $level $words $code]                                          ;# for debugging
                incr code
            }
        }
        set index 0
        foreach list [lsort -index 0 -integer -decreasing $values] {                                      ;# sort by number of words
            foreach {length level words} $list {}
#           foreach {length level words code} $list {}                                                              ;# for debugging
            set messageLine($index,words) $words
            set messageLine($index,length) $length
            set messageLine($index,level) $level
#           set messageLine($index,code) $code                                                                      ;# for debugging
            if {([lsearch -exact $words Got] >= 0) && ([lsearch -regexp $words signal:?] >= 0)} {
                set messageLine($index,signal) {}                        ;# signal type message of the form: "... Got signal %d ..."
            }
            incr index
        }
        set messageLine(number) $index
    }

    # return differences between ordered list of words so that, for example:
    # "/usr/sbin/mysqld: Normal shutdown" and "%s: Normal shutdown" differ by 1
    proc differences {words1 words2} {
        set number 0
        foreach word1 $words1 word2 $words2 {
            if {![string equal $word1 $word2]} {incr number}
        }
        return $number
    }

    proc find {message} {                      ;# try to find the message in the pool of known messages by doing some fuzzy matching
        variable messageLine

        set words [words $message]
        set length [llength $words]
        set minimum 0x7FFFFFFF
        set best -1
        for {set index 0} {$index < $messageLine(number)} {incr index} {
            if {$messageLine($index,length) > $length} continue                       ;# do not attempt comparison with longer lines
            set score [differences $messageLine($index,words) $words]
            if {$score == 0} {
                return $index                                                                                   ;# found exact match
            }
            if {$score < $minimum} {
                set minimum $score
                set best $index
            }
        }
        if {(2 * $minimum) >= $length} { ;# consider that there was no match if differences exceed or equal half the number of words
            return -1
        }
        return $best
    }

    proc words {string} {
        regsub -all {\s+} $string { } string
        return [split $string]
    }

    # some required initializations follow:
    variable hardCodedMessages
    variable codeLevel

    set emergency 0; set alert 1; set critical 2; set error 3; set warning 4; set notice 5; set info 6; set debug 7;

    # Below are the fisrt line or first part (that fits in this source code 132 columns) of error messages found directly in the
    # MySQL source code or reported once as unknown (only the first part of a message is considered necessary for a good match)
    set messages [list\
        $info      "%s: Normal shutdown"\
        $info      "%s: Shutdown Complete"\
        $error     "%s, errno=%d, io cache code=%d"\
        $error     "%s. Failed executing load from '%s'"\
        $error     "Aborting"\
        $debug     "After lock_thread_count"\
        $debug     "Before Lock_thread_count"\
        $critical  "Binary log is not open"\
        $critical  "binlog truncated in the middle of event"\
        $critical  "bogus data in log event"\
        $alert     "Can't alloc memory for udf function: name"\
        $alert     "Can't allocate memory for udf structures"\
        $alert     "Can't create interrupt-thread (error %d, errno: %d)"\
        $alert     "Can't create thread-keys"\
        $alert     "Can't init databases"\
        $alert     "Can't open the mysql/func table. Please run the mysql_install_db script to create it."\
        $error     "Can't start server: Bind on TCP/IP port: Address already in use"\
        $critical  "Client requested master to start replication from impossible position"\
        $critical  "Connection to master failed: %s"\
        $error     "Could not connect to master while fetching table '%-64s.%-64s'"\
        $critical  "Could not find first log file name in binary log index file"\
        $error     "Could not find first log while counting relay log space"\
        $critical  "could not find next log"\
        $alert     "Could not initialize failsafe replication thread"\
        $error     "Could not parse relay log event entry. The possible reasons are: the master's"\
        $error     "Could not re-open the binlog index file during log purge for write"\
        $error     "Could not truncate the binlog index file during log purge for write"\
        $error     "Could not use %s for logging (error %d). Turning logging off for the whole duration of the MySQL server"\
        $error     "create_table_from_dump: could not open created table"\
        $error     "create_table_from_dump: failed in handler::net_read_dump()"\
        $error     "create_table_from_dump: failed to drop the table"\
        $critical  "create_table_from_dump: out of memory"\
        $alert     "Do you already have another mysqld server running on port: %d ?"\
        $alert     "Do you already have another mysqld server running on socket: %s ?"\
        $error     "Error: Can't create thread to kill server"\
        $error     "Error: Couldn't repair table: %s.%s"\
        $error     "Error: Got error during commit;  Binlog is not up to date!"\
        $error     "Error: using --replicate-same-server-id in conjunction with --log-slave-updates is impossible, it would lead"\
        $error     "Error: when opening HEAP table, could not allocate memory to write 'DELETE FROM `%s`.`%s`' to the binary log"\
        $error     "error '%s' on query '%s'"\
        $error     "Error '%s' on query '%s'. Default database: '%s'"\
        $error     "Error '%s' running load data infile"\
        $error     "Error checking master version: %s"\
        $error     "Error counting relay log space"\
        $error     "Error deleting %s during purge"\
        $error     "Error in Append_block event: could not open file '%s'"\
        $error     "Error in Log_event::read_log_event(): '%s', data_len: %d, event_type: %d"\
        $error     "Error initializing relay log position: %s"\
        $error     "Error on COM_BINLOG_DUMP: %d %s, will retry in %d secs"\
        $error     "Error on COM_REGISTER_SLAVE: %d '%s'"\
        $critical  "error reading log entry"\
        $error     "Error reading log file name from master info file "\
        $error     "Error reading log file position from master info file"\
        $error     "Error reading master configuration"\
        $error     "Error reading packet from server: %s (read_errno %d,server_errno=%d)"\
        $error     "Error reading relay log event: %s"\
        $error     "Error reading slave log configuration"\
        $error     "Error running query, slave SQL thread aborted. Fix the problem, and restart the slave SQL thread with \"SLAVE"\
        $error     "Error updating slave list: %s"\
        $debug     "Exiting main thread"\
        $error     "Failed during slave I/O thread initialization"\
        $error     "Failed during slave thread initialization"\
        $error     "Failed executing load from '%s'"\
        $error     "Failed in my_net_write"\
        $error     "Failed in my_net_write, writing to stderr instead: %s"\
        $error     "Failed in open_log() called from init_relay_log_info()"\
        $error     "Failed in send_fields"\
        $error     "Failed in send_file() %s"\
        $error     "failed in send_file()"\
        $error     "failed on net_flush()"\
        $error     "Failed on request_dump()"\
        $error     "Failed to create a cache on log (file '%s')"\
        $error     "Failed to create a cache on master info file (file '%s')"\
        $error     "Failed to create a cache on relay log info file '%s'"\
        $error     "Failed to create a new master info file (file '%s', errno %d)"\
        $error     "Failed to create a new relay log info file (file '%s', errno %d)"\
        $warning   "Failed to create slave threads"\
        $error     "Failed to flush master info file"\
        $error     "Failed to flush relay log info file"\
        $notice    "Failed to initialize the master info structure"\
        $error     "Failed to allocate memory for the master info structure"\
        $error     "Failed to open log (file '%s', errno %d)"\
        $error     "Failed to open the existing relay log info file '%s' (errno %d)"\
        $error     "Failed to open the existing master info file (file '%s', errno %d)"\
        $error     "Failed to open the relay log 'FIRST' (relay_log_pos 4)"\
        $error     "Failed to open the relay log '%s' (relay_log_pos %s)"\
        $critical  "Fatal error: Can't open privilege tables: %s"\
        $critical  "Fatal error: Can't lock privilege tables: %s"\
        $error     "Fatal error running LOAD DATA INFILE on table '%s'. Default database: '%s'"\
        $error     "fetch_master_table: Error in mysql_init()"\
        $error     "fetch_master_table: failed on create table "\
        $error     "fetch_master_table: failed on table dump request "\
        $error     "Found an entry in the 'db' table with empty database name; Skipped"\
        $critical  "Found invalid password for user: '%s'@'%s'; Ignoring user"\
        $error     "Found next-recordlink that points outside datafile at %s"\
        $warning   "Found old style password for user '%s'. Ignoring user. (You may want to restart mysqld using --old-protocol)"\
        $critical  "ft_read_first: Got error %d when reading table %s"\
        $critical  "ft_read_next: Got error %d when reading table %s"\
        $error     "Got error %d from pthread_cond_timedwait"\
        $info      "Got signal %d to shutdown mysqld"\
        $error     "ha_myisam::net_read_dump - read error "\
        $critical  "I/O error reading log event"\
        $error     "I/O error reading the header from the binary log, errno=%d, io cache code=%d"\
        $info      "InnoDB: Log file ./ib_logfile0 did not exist: new to be created"\
        $info      "InnoDB: Log file ./ib_logfile1 did not exist: new to be created"\
        $warning   "InnoDB: Out of memory in additional memory pool."\
        $info      "InnoDB: Setting file ./ibdata1 size to 10 MB"\
        $info      "InnoDB: Started"\
        $info      "InnoDB: Starting shutdown..."\
        $info      "InnoDB: Shutdown completed"\
        $warning   "InnoDB: Warning: shutting down a not properly started"\
		$warning   "InnoDB: Warning: difficult to find free blocks from"\
        $error     "Innodb could not find key n:o %u with name %s from dict cache for table %s"\
        $error     "load_des_file:  Found wrong key_number: %c"\
        $error     "log %s listed in the index, but failed to stat"\
        $warning   "Log entry on master is longer than max_allowed_packet (%ld) on slave. If the entry is correct, restart the"\
        $critical  "log event entry exceeded max_allowed_packet; Increase max_allowed_packet on master"\
        $error     "Master reported an unrecognized MySQL version. Note that 4.0 slaves can't replicate a 5.0 or newer master."\
        $error     "Master reported NULL for the version"\
        $error     "Master reported unrecognized MySQL version"\
        $error     "Master returned no rows for SELECT VERSION()"\
        $critical  "memory allocation failed reading log event"\
        $critical  "Misconfigured master - server id was not set"\
        $error     "mysqld: Got error %d from select"\
        $info      "mysqld ended"\
        $info      "mysqld restarted"\
        $info      "mysqld started"\
        $error     "mysql_ha_read: Got error %d when reading table '%s'"\
        $error     "Network read error downloading '%s' from master"\
        $error     "next log '%s' is not active"\
        $info      "next log '%s' is currently active"\
        $error     "next log error=%d,offset=%s,log=%s"\
        $notice    "Note: Found %s of %s rows when repairing '%s'"\
        $notice    "Note: Retrying repair of: '%s' with keycache"\
        $notice    "Note: Retrying repair of: '%s' without quick"\
        $notice    "Note: Use_count: Wrong count %lu for root"\
        $notice    "Note: Use_count: Wrong count for key at %lx, %lu should be %lu"\
        $warning   "Old 'user' table. (Check README or the Reference manual). Continuing --old-protocol"\
        $emergency "Out of memory while recording slave event"\
        $error     "Query '%s' caused different errors on master and slave. Error on master: '%s' (%d), Error on slave: '%s' (%d)."\
        $error     "Read invalid event from master: '%s', master could be corrupt but a more likely cause of this is a bug"\
        $error     "read_const: Got error %d when reading table %s"\
        $error     "read_first_with_key: Got error %d when reading table"\
        $error     "read_key: Got error %d when reading table '%s'"\
        $error     "read_last_with_key: Got error %d when reading table"\
        $error     "read_next: Got error %d when reading table %s"\
        $error     "read_next_with_key: Got error %d when reading table %s"\
        $error     "read_prev_with_key: Got error %d when reading table: %s"\
        $error     "request_table_dump: Buffer overrun"\
        $error     "request_table_dump: Error sending the table dump command"\
        $error     "Rolling back unfinished transaction (no COMMIT or ROLLBACK) from relay log. Probably cause is that the master"\
        $error     "Server id not set, will not start slave"\
        $error     "Slave: %s, Error_code: %d"\
        $warning   "Slave: connected to master '%s@%s:%d', replication resumed in log '%s' at position %s"\
        $error     "Slave: did not get the expected error running query from master - expected: '%s' (%d), got '%s' (%d)"\
        $error     "Slave: Error in Append_block event: could not open file '%s'"\
        $error     "Slave: Error in Append_block event: write to '%s' failed"\
        $error     "Slave: Error in Create_file event: could not open file '%s'"\
        $error     "Slave: Error in Create_file event: could not write to file '%s'"\
        $error     "Slave: Error in Create_file event: write to '%s' failed"\
        $error     "Slave: Error in Exec_load event: could not open file '%s'"\
        $error     "Slave: Error in Exec_load event: file '%s' appears corrupted"\
        $error     "Slave: failed dump request, reconnecting to try again, log '%s' at postion %s"\
        $warning   "Slave: load data infile on table '%s' at log position %s in log '%s' produced %ld warning(s). Default"\
        $error     "Slave: query '%s' partially completed on the master and was aborted. There is a chance that your master is"\
        $warning   "Slave: received 0 length packet from server, apparent master shutdown: %s"\
        $error     "Slave I/O: error writing Append_block event to relay log"\
        $error     "Slave I/O: error writing Create_file event to relay log"\
        $error     "Slave I/O: error writing Exec_load event to relay log"\
        $error     "Slave I/O: failed requesting download of '%s'"\
        $critical  "Slave I/O: out of memory for Load event"\
        $info      "Slave I/O thread: connected to master '%s@%s:%d', replication started in log '%s' at position %s"\
        $error     "Slave I/O thread: debugging abort"\
        $error     "Slave I/O thread: error %s to master '%s@%s:%d': Error: '%s' errno: %d retry-time: %d retries: %d"\
        $error     "Slave I/O thread: error in mc_mysql_init()"\
        $error     "Slave I/O thread: failed dump request, reconnecting to try again, log '%s' at position %s"\
        $error     "Slave I/O thread: Failed reading log event, reconnecting to retry, log '%s' position %s"\
        $error     "Slave I/O thread aborted while waiting for relay log space"\
        $error     "Slave I/O thread could not queue event from master"\
        $error     "Slave I/O thread exiting, read up to log '%s', position %s"\
        $info      "Slave I/O thread initialized"\
        $error     "Slave I/O thread killed during or after a reconnect done to recover from failed read"\
        $error     "Slave I/O thread killed during or after reconnect"\
        $error     "Slave I/O thread killed while connecting to master"\
        $error     "Slave I/O thread killed while reading event"\
        $error     "Slave I/O thread killed while requesting master dump"\
        $error     "Slave I/O thread killed while retrying master dump"\
        $error     "Slave I/O thread killed while waiting to reconnect after a failed read"\
        $error     "Slave SQL thread: I/O error reading event(errno: %d  cur_log->error: %d)"\
        $error     "Slave SQL thread exiting, replication stopped in log '%s' at position %s"\
        $info      "Slave SQL thread initialized, starting replication in log '%s' at position %s, relay log '%s' position: %s"\
        $error     "Table %s had a open data handler in reopen_table"\
        $warning   "TCP/IP or --enable-named-pipe should be configured on NT OS"\
        $error     "TCP/IP unavailable or disabled with --skip-networking; no available interfaces"\
        $error     "The first log %s failed to stat during purge"\
        $error     "there is an unfinished transaction in the relay log (could find neither COMMIT nor ROLLBACK in the relay log);"\
        $critical  "unknown error reading log event on the master"\
        $warning   "Warning: Asked for %ld thread stack, but got %ld"\
        $warning   "Warning: Can't create test file %s"\
        $warning   "Warning: Can't create thread to handle bootstrap"\
        $warning   "Warning: Can't create thread to handle named pipes"\
        $warning   "Warning: Can't create thread to handle shutdown requests"\
        $warning   "Warning: Can't create thread to manage maintenance"\
        $warning   "Warning: Changed limits: max_connections: %ld  table_cache: %ld"\
        $warning   "Warning: Checking table:   '%s'"\
        $warning   "Warning: Could not open BDB table %s.%s after rename"\
        $warning   "Warning: Could not remove tmp table: '%s', error: %d"\
        $warning   "Warning: DosSetRelMaxFH couldn't increase number of open files to more than %d"\
        $warning   "Warning: Failed to lock memory. Errno: %d"\
        $warning   "Warning: Got signal: %d, error: %d"\
        $warning   "Warning: Got signal %d from thread %d"\
        $warning   "Warning: listen() on TCP/IP failed with error %d"\
        $warning   "Warning: listen() on Unix socket failed with error %d"\
        $warning   "Warning: Recovering table: '%s'"\
        $warning   "Warning: setrlimit could not change the size of core files to 'infinity';  We may not be able to generate a"\
        $warning   "Warning: setrlimit couldn't increase number of open files to more than %lu (request: %u)"\
        $warning   "Warning: setrlimit returned ok, but didn't change limits. Max open files is %ld (request: %u)"\
        $warning   "Warning: Setting lower_case_table_names=1 because file system %s is case insensitive"\
        $warning   "Warning: Setting lower_case_table_names=2 because file system for %s is case insensitive"\
        $warning   "Warning: The position in the binary log can't be less than %d."\
        $warning   "Warning: Wrong use count: %u (should be %u) for tree at %lx"\
        $warning   "Warning: You have enabled the binary log, but you haven't set server-id to a non-zero value: we force server"\
        $warning   "Warning: You have forced lower_case_table_names to 0 through a command-line option, even though your file"\
        $warning   "Warning: you need to use --log-bin to make --log-slave-updates work. Now disabling --log-slave-updates."\
        $warning   "Warning: You should set server-id to a non-0 value if master_host is set; we force server id to 2, but this"\
        $error     "While trying to obtain the list of slaves from the master '%s:%d', user '%s' got the following error: '%s'"\
        $error     "Wrong record length %s of %s at %s"\
        $error     "Wrong tree: Found right == left"\
        $error     "Wrong tree: Found two red in a row"\
        $error     "Wrong tree: Incorrect black-count: %d - %d"\
        $error     "Wrong tree: Parent doesn't point at parent"\
        $error     "X509 ciphers mismatch: should be '%s' but is '%s'"\
        $error     "X509 issuer mismatch: should be '%s' but is '%s'"\
        $error     "X509 subject mismatch: '%s' vs '%s'"\
    ]

    # for hardcoded messages above:
    set code 0
    foreach {level message} $messages {
        set message [string trim $message]
        regsub -all {\n.*$} $message {} message                          ;# only the first generated message line is matched against
        lappend hardCodedMessages $message
        set codeLevel($code) $level
        incr code
    }
    unset code level message messages

    # for international messages as found in /usr/share/mysql/english/errmsg.txt for example, while importance level can be
    # determined by looking at sources: include/mysqld_error.h and Docs/mysqld_error.txt
    array set codeLevel [list\
        1000 $debug 1001 $debug 1002 $debug 1003 $debug 1004 $alert 1005 $critical 1006 $critical 1007 $critical 1008 $critical\
            1009 $critical\
        1010 $critical 1011 $error 1012 $alert 1013 $warning 1014 $emergency 1015 $alert 1016 $alert 1017 $critical 1018 $alert\
            1019 $alert\
        1020 $error 1021 $emergency 1022 $error 1023 $error 1024 $critical 1025 $critical 1026 $critical 1027 $warning 1028 $error\
            1029 $error\
        1030 $alert 1031 $error 1032 $error 1033 $alert 1034 $alert 1035 $alert 1036 $critical 1037 $alert 1038 $alert 1039 $error\
        1040 $error 1041 $alert 1042 $warning 1043 $critical 1044 $error 1045 $error 1046 $error 1047 $error 1048 $error\
            1049 $error\
        1050 $error 1051 $error 1052 $warning 1053 $info 1054 $error 1055 $error 1056 $error 1057 $error 1058 $error 1059 $error\
        1060 $error 1061 $error 1062 $error 1063 $error 1064 $error 1065 $error 1066 $error 1067 $error 1068 $error 1069 $error\
        1070 $error 1071 $error 1072 $error 1073 $error 1074 $error 1075 $error 1076 $info 1077 $info 1078 $info 1079 $info\
        1080 $error 1081 $critical 1082 $error 1083 $error 1084 $error 1085 $alert 1086 $error 1087 $info 1088 $info 1089 $error\
        1090 $warning 1091 $error 1092 $info 1093 $error 1094 $error 1095 $error 1096 $warning 1097 $error 1098 $error 1099 $error\
        1100 $error 1101 $error 1102 $error 1103 $error 1104 $warning 1105 $error 1106 $error 1107 $error 1108 $error 1109 $error\
        1110 $warning 1111 $error 1112 $error 1113 $error 1114 $critical 1115 $error 1116 $error 1117 $error 1118 $error\
            1119 $emergency\
        1120 $warning 1121 $error 1122 $error 1123 $error 1124 $error 1125 $error 1126 $critical 1127 $critical 1128 $critical\
            1129 $error\
        1130 $error 1131 $error 1132 $error 1133 $warning 1134 $info 1135 $alert 1136 $error 1137 $error 1138 $error 1139 $error\
        1140 $warning 1141 $error 1142 $error 1143 $error 1144 $error 1145 $error 1146 $error 1147 $error 1148 $error 1149 $error\
        1150 $error 1151 $error 1152 $error 1153 $critical 1154 $critical 1155 $critical 1156 $critical 1157 $critical\
            1158 $critical 1159 $error\
        1160 $critical 1161 $error 1162 $error 1163 $error 1164 $error 1165 $error 1166 $error 1167 $error 1168 $error 1169 $error\
        1170 $error 1171 $error 1172 $warning 1173 $warning 1174 $warning 1175 $warning 1176 $error 1177 $error 1178 $notice\
            1179 $info\
        1180 $critical 1181 $critical 1182 $error 1183 $error 1184 $error 1185 $error 1186 $error 1187 $error 1188 $error\
            1189 $error\
        1190 $error 1191 $error 1192 $error 1193 $error 1194 $alert 1195 $alert 1196 $warning 1197 $warning 1198 $error 1199 $error\
        1200 $error 1201 $error 1202 $critical 1203 $error 1204 $warning 1205 $error 1206 $alert 1207 $error 1208 $error\
            1209 $error\
        1210 $error 1211 $error 1212 $error 1213 $error 1214 $warning 1215 $error 1216 $error 1217 $error 1218 $error 1219 $error\
        1220 $error 1221 $error 1222 $error 1223 $error 1224 $error 1225 $error 1226 $error 1227 $error 1228 $error 1229 $error\
        1230 $error 1231 $error 1232 $error 1233 $error 1234 $error 1235 $warning 1236 $critical 1237 $warning 1238 $error\
    ]

    unset debug info notice warning error critical alert emergency

}
