Additional logging questions

Discussion in 'Java' started by Novice, Feb 27, 2012.

  1. Novice

    Novice Guest

    I _think_ these supplementary questions will be a lot less of a struggle
    than the others in "Aspect questions" thread. That thread is getting on
    the long side so I thought I'd ask these in a new thread.

    Basically, I'm looking for advice on what should always be logged by
    every class. I understand now that every class is going to have its own
    logger but what should be logged?

    Or to put it another way, are there cases where a class won't log at all?

    I'm thinking of things like Enums. If I have an enum that lists the days
    of the week, there's not much to go wrong there and I'm not likely to
    throw exceptions or even have a try/catch block. So should it just be
    left so that it isn't logging at all? Or should there be some standard
    bare-minimum sort of logging, like an entering() and existing(), even if
    nothing else of interest goes on?

    What about holder classes? I'm not sure if I'm using the terminology
    correctly but I'm thinking of a class where you simply store related bits
    of data, like a Name class whose constructor insists on a first name and
    a last name and then supplies getters and setters so that another class
    can ask for just the first name or just the last name? (Let's pretend
    that everyone has exactly one given name and one surname, no exceptions,
    just to keep this simple). This could be an awfully barebones class if it
    only had a two line constructor and one line getters and setters. Should
    it log anyway?

    My feeling is that Lew would say NOT to log unless there was a good
    reason to log and then cite several good reasons to log. I'm not sure if
    something like an enum or a holder class (if I've used the term
    correctly) would EVER justify logging though.

    I may have completely misread Lew and, if so, I'm sorry. Maybe this is
    another premature leap....

    Some of the rest of you may differ dramatically on what should be logged
    and when it is okay not to bother. I hope some of you can share those
    thoughts with me.

    Basically, I'm just about ready to start getting loggers for each and
    every class in the project I'm working on now (with plans to do the same
    in every project as I create it or return to it). But I don't want to do
    too much logging either.....

    --
    Novice
     
    Novice, Feb 27, 2012
    #1
    1. Advertising

  2. Novice

    Arne Vajhøj Guest

    On 2/26/2012 9:02 PM, Novice wrote:
    > I _think_ these supplementary questions will be a lot less of a struggle
    > than the others in "Aspect questions" thread. That thread is getting on
    > the long side so I thought I'd ask these in a new thread.
    >
    > Basically, I'm looking for advice on what should always be logged by
    > every class. I understand now that every class is going to have its own
    > logger but what should be logged?
    >
    > Or to put it another way, are there cases where a class won't log at all?
    >
    > I'm thinking of things like Enums. If I have an enum that lists the days
    > of the week, there's not much to go wrong there and I'm not likely to
    > throw exceptions or even have a try/catch block. So should it just be
    > left so that it isn't logging at all? Or should there be some standard
    > bare-minimum sort of logging, like an entering() and existing(), even if
    > nothing else of interest goes on?
    >
    > What about holder classes? I'm not sure if I'm using the terminology
    > correctly but I'm thinking of a class where you simply store related bits
    > of data, like a Name class whose constructor insists on a first name and
    > a last name and then supplies getters and setters so that another class
    > can ask for just the first name or just the last name? (Let's pretend
    > that everyone has exactly one given name and one surname, no exceptions,
    > just to keep this simple). This could be an awfully barebones class if it
    > only had a two line constructor and one line getters and setters. Should
    > it log anyway?
    >
    > My feeling is that Lew would say NOT to log unless there was a good
    > reason to log and then cite several good reasons to log. I'm not sure if
    > something like an enum or a holder class (if I've used the term
    > correctly) would EVER justify logging though.
    >
    > I may have completely misread Lew and, if so, I'm sorry. Maybe this is
    > another premature leap....
    >
    > Some of the rest of you may differ dramatically on what should be logged
    > and when it is okay not to bother. I hope some of you can share those
    > thoughts with me.
    >
    > Basically, I'm just about ready to start getting loggers for each and
    > every class in the project I'm working on now (with plans to do the same
    > in every project as I create it or return to it). But I don't want to do
    > too much logging either.....


    You should log the information you expect potentially could be
    useful when troubleshooting a problem.

    And as a general rule, then if any doubt then log, because it
    is usually better to have too much logging than too little
    logging.

    I do not see any need for logging in an enum or in a pure
    data class (holder class).

    But please add a toString method in your data class, so
    when the class with real login in that uses the data class
    can log it and you get something useful in the log about the
    data.

    Arne
     
    Arne Vajhøj, Feb 27, 2012
    #2
    1. Advertising

  3. Novice

    Novice Guest

    Arne Vajhøj <> wrote in
    news:4f4ae583$0$281$:

    > On 2/26/2012 9:02 PM, Novice wrote:
    >> I _think_ these supplementary questions will be a lot less of a
    >> struggle than the others in "Aspect questions" thread. That thread is
    >> getting on the long side so I thought I'd ask these in a new thread.
    >>
    >> Basically, I'm looking for advice on what should always be logged by
    >> every class. I understand now that every class is going to have its
    >> own logger but what should be logged?
    >>
    >> Or to put it another way, are there cases where a class won't log at
    >> all?
    >>
    >> I'm thinking of things like Enums. If I have an enum that lists the
    >> days of the week, there's not much to go wrong there and I'm not
    >> likely to throw exceptions or even have a try/catch block. So should
    >> it just be left so that it isn't logging at all? Or should there be
    >> some standard bare-minimum sort of logging, like an entering() and
    >> existing(), even if nothing else of interest goes on?
    >>
    >> What about holder classes? I'm not sure if I'm using the terminology
    >> correctly but I'm thinking of a class where you simply store related
    >> bits of data, like a Name class whose constructor insists on a first
    >> name and a last name and then supplies getters and setters so that
    >> another class can ask for just the first name or just the last name?
    >> (Let's pretend that everyone has exactly one given name and one
    >> surname, no exceptions, just to keep this simple). This could be an
    >> awfully barebones class if it only had a two line constructor and one
    >> line getters and setters. Should it log anyway?
    >>
    >> My feeling is that Lew would say NOT to log unless there was a good
    >> reason to log and then cite several good reasons to log. I'm not sure
    >> if something like an enum or a holder class (if I've used the term
    >> correctly) would EVER justify logging though.
    >>
    >> I may have completely misread Lew and, if so, I'm sorry. Maybe this
    >> is another premature leap....
    >>
    >> Some of the rest of you may differ dramatically on what should be
    >> logged and when it is okay not to bother. I hope some of you can
    >> share those thoughts with me.
    >>
    >> Basically, I'm just about ready to start getting loggers for each and
    >> every class in the project I'm working on now (with plans to do the
    >> same in every project as I create it or return to it). But I don't
    >> want to do too much logging either.....

    >
    > You should log the information you expect potentially could be
    > useful when troubleshooting a problem.
    >
    > And as a general rule, then if any doubt then log, because it
    > is usually better to have too much logging than too little
    > logging.
    >
    > I do not see any need for logging in an enum or in a pure
    > data class (holder class).
    >
    > But please add a toString method in your data class, so
    > when the class with real login in that uses the data class
    > can log it and you get something useful in the log about the
    > data.
    >

    Sorry, I'm not following you.

    Are you saying that the toString() method needs to be there to turn
    things like references into meaningful information? I know that a
    reference to something like a JFrame is not going to be very meaningful
    and would rather display the name given the JFrame via setName(). Or are
    you saying something quite different?


    --
    Novice
     
    Novice, Feb 27, 2012
    #3
  4. Novice

    Lew Guest

    Novice wrote:
    > Arne Vajhøj wrote:
    >> Novice wrote:
    >>> Basically, I'm looking for advice on what should always be logged by
    >>> every class. I understand now that every class is going to have its
    >>> own logger but what should be logged?


    You log what reveals to someone reading the log what they want to know.

    >>> Or to put it another way, are there cases where a class won't log at
    >>> all?


    That depends on the logging level set at runtime, doesn't it?

    If a class is unable to log anything, and that omission deprives someone of
    necessary information when they're relying on the log, it's a mistake.

    If no one ever needs information from that class out of the log, it can get
    away with not logging.

    >>> I'm thinking of things like Enums. If I have an enum that lists the


    Enums are classes. The same considerations apply as for any other class.

    >>> days of the week, there's not much to go wrong there and I'm not


    Is there? I trust you - there's not.

    >>> likely to throw exceptions or even have a try/catch block. So should
    >>> it just be left so that it isn't logging at all? Or should there be
    >>> some standard bare-minimum sort of logging, like an entering() and
    >>> existing(), even if nothing else of interest goes on?


    Good questions. Answer wisely, Grasshopper.

    >>> What about holder classes? I'm not sure if I'm using the terminology


    You are.

    >>> correctly but I'm thinking of a class where you simply store related
    >>> bits of data, like a Name class whose constructor insists on a first
    >>> name and a last name and then supplies getters and setters so that
    >>> another class can ask for just the first name or just the last name?
    >>> (Let's pretend that everyone has exactly one given name and one
    >>> surname, no exceptions, just to keep this simple). This could be an
    >>> awfully barebones class if it only had a two line constructor and one
    >>> line getters and setters. Should it log anyway?


    Logging is generally for state changes.

    >>> My feeling is that Lew would say NOT to log unless there was a good
    >>> reason to log and then cite several good reasons to log. I'm not sure


    Don't take advice from your fantasy of me unless it's good advice. Don't take
    advice from the real me unless it's good advice, either.

    >>> if something like an enum or a holder class (if I've used the term
    >>> correctly) would EVER justify logging though.


    Sure.

    Depends on what's in it, doesn't it?

    >>> I may have completely misread Lew and, if so, I'm sorry. Maybe this
    >>> is another premature leap....


    Since I never before said what to log or not log that you've seen, there's
    been nothing to misread, has there?

    Nothing to read, nothing to misread. It's a simple equation.

    >>> Some of the rest of you may differ dramatically on what should be
    >>> logged and when it is okay not to bother. I hope some of you can
    >>> share those thoughts with me.
    >>>
    >>> Basically, I'm just about ready to start getting loggers for each and
    >>> every class in the project I'm working on now (with plans to do the
    >>> same in every project as I create it or return to it). But I don't
    >>> want to do too much logging either.....

    >>
    >> You should log the information you expect potentially could be
    >> useful when troubleshooting a problem.


    This requires that you think like a useful person, not a computer programmer.

    When you're troubleshooting a log, you don't have code in front of you. You
    have what the log tells you. It had better God-damned tell you what you need,
    because you wouldn't be looking if someone weren't breathing down your neck.
    No fancy "***********************=============" strings. Logs are dense,
    multi-mega- or gigabyte beasts of tightly printed strings.

    Ops personnel read logs. Ops personnel think programmers are children. I had
    an ops mentor who told me, "We love getting the programmers from [the
    development location] here for six months. They go back to coding _changed_!"

    Other times they're cursing the programmers who wrote such lame logging
    statements.

    >> And as a general rule, then if any doubt then log, because it
    >> is usually better to have too much logging than too little
    >> logging.
    >>
    >> I do not see any need for logging in an enum or in a pure
    >> data class (holder class).


    Some enums.

    Like any other class, it depends on what it does. But generally you log state
    changes, i.e., behavioral methods (not usually attributes). You log anything
    that is weird. You log errors and warnings.

    You pick appropriate logging levels. Here's my log4j idiom:

    public void loadResource()
    {
    logger.debug("");

    final BufferedReader reader;
    try
    {
    reader = new BufferedReader(new InputStreamReader(getClass()
    .getResourceAsStream("/config/configuration.txt")));
    }
    catch(IOException exc)
    {
    String msg = "Cannot open configuration. "+ exc.getLocalizedMessage();
    logger.error(msg, exc);
    throw new IllegalStateException(msg, exc);
    }
    assert reader != null;

    try
    {
    // read the Reader, etc.
    }
    catch(IOException exc)
    {
    String msg = "Cannot read configuration. "+ exc.getLocalizedMessage();
    logger.error(msg, exc);
    throw new IllegalStateException(msg, exc);
    }
    finally
    {
    try
    {
    reader.close();
    }
    catch(IOException exc)
    {
    String msg = "Cannot close configuration. "
    + exc.getLocalizedMessage();
    logger.warn(msg, exc);
    }
    }
    }

    Note the multiple uses of 'logger' (an instance member) in that method.

    >> But please add a toString method in your data class, so
    >> when the class with real login in that uses the data class
    >> can log it and you get something useful in the log about the
    >> data.
    >>

    > Sorry, I'm not following you.
    >
    > Are you saying that the toString() method needs to be there to turn
    > things like references into meaningful information? I know that a
    > reference to something like a JFrame is not going to be very meaningful
    > and would rather display the name given the JFrame via setName(). Or are
    > you saying something quite different?


    'toString()' should always give a useful way to identify the specific instance.

    It should depend on (and usually only on) the same fields used to drive
    'hashCode()' and 'equals()' and if supported, 'compareTo()' (which should
    always be consistent with each other).

    --
    Lew
    Honi soit qui mal y pense.
    http://upload.wikimedia.org/wikipedia/commons/c/cf/Friz.jpg
     
    Lew, Feb 27, 2012
    #4
  5. Novice

    Novice Guest

    Lew <> wrote in news:jif6ua$3cm$:

    > Novice wrote:
    >> Arne Vajhøj wrote:
    >>> Novice wrote:
    >>>> Basically, I'm looking for advice on what should always be logged
    >>>> by every class. I understand now that every class is going to have
    >>>> its own logger but what should be logged?

    >
    > You log what reveals to someone reading the log what they want to
    > know.
    >
    >>>> Or to put it another way, are there cases where a class won't log
    >>>> at all?

    >
    > That depends on the logging level set at runtime, doesn't it?
    >

    Right.

    > If a class is unable to log anything, and that omission deprives
    > someone of necessary information when they're relying on the log, it's
    > a mistake.
    >

    Another good rule of thumb.

    > If no one ever needs information from that class out of the log, it
    > can get away with not logging.
    >

    That seems reasonable to me. I was pretty sure you were not going to
    advocate logging for the sake of logging and you didn't let me down ;-)

    >>>> I'm thinking of things like Enums. If I have an enum that lists the

    >
    > Enums are classes. The same considerations apply as for any other
    > class.
    >
    >>>> days of the week, there's not much to go wrong there and I'm not

    >
    > Is there? I trust you - there's not.
    >

    Well, I'm thinking of enums like mine which are akin to edits. For
    instance, I use preferences for some of my programs so I created a
    PreferenceTrees enum. It has only two values, SYSTEM and USER. My
    PreferenceUtils class uses PreferenceTrees as a type in all of its
    methods so that someone invoking those methods can ONLY choose
    PreferenceTrees.SYSTEM or PreferenceTrees.USER, there's no possibility
    that someone is going to misspell "System", which could certainly happen
    if the method was expecting a String parameter. The PreferencesTree enum
    is trivial and seems unlikely to ever blow up.

    But if an enum did more complicated logic, like looking up a day number
    and turning it into a day name in a foreign language, then sure, I can
    see that something might go awry and justify logging. The logic that
    looks up how to say "Monday" in Latvian or Turkish could throw an
    exception that should be logged. But my enums are mostly like the
    PreferenceTrees enum that I mentioned.


    >>>> likely to throw exceptions or even have a try/catch block. So
    >>>> should it just be left so that it isn't logging at all? Or should
    >>>> there be some standard bare-minimum sort of logging, like an
    >>>> entering() and existing(), even if nothing else of interest goes
    >>>> on?

    >
    > Good questions. Answer wisely, Grasshopper.
    >
    >>>> What about holder classes? I'm not sure if I'm using the
    >>>> terminology

    >
    > You are.
    >

    Some people call these data classes too, if I'm not mistaken. I find that
    a bit more descriptive but maybe that's just me.

    >>>> correctly but I'm thinking of a class where you simply store
    >>>> related bits of data, like a Name class whose constructor insists
    >>>> on a first name and a last name and then supplies getters and
    >>>> setters so that another class can ask for just the first name or
    >>>> just the last name? (Let's pretend that everyone has exactly one
    >>>> given name and one surname, no exceptions, just to keep this
    >>>> simple). This could be an awfully barebones class if it only had a
    >>>> two line constructor and one line getters and setters. Should it
    >>>> log anyway?

    >
    > Logging is generally for state changes.
    >
    >>>> My feeling is that Lew would say NOT to log unless there was a good
    >>>> reason to log and then cite several good reasons to log. I'm not
    >>>> sure

    >
    > Don't take advice from your fantasy of me unless it's good advice.
    > Don't take advice from the real me unless it's good advice, either.
    >

    Absolutely. I'm just trying to think along the same lines as you've
    proposed in your posts. I hope I haven't made an unwarranted leap again.

    >>>> if something like an enum or a holder class (if I've used the term
    >>>> correctly) would EVER justify logging though.

    >
    > Sure.
    >
    > Depends on what's in it, doesn't it?
    >

    See my remarks about enums above. The same would apply to a holder class.
    If it's a one line getter or setter, there's probably not much to go
    wrong and logging is probably inappropriate. But if it's doing something
    that could fail, sure, logging would make sense.

    >>>> I may have completely misread Lew and, if so, I'm sorry. Maybe this
    >>>> is another premature leap....

    >
    > Since I never before said what to log or not log that you've seen,
    > there's been nothing to misread, has there?
    >
    > Nothing to read, nothing to misread. It's a simple equation.
    >

    Understood. I'm just extrapolating the general principle that you've been
    stating, which I would paraphrase (roughly) as "don't do anything
    slavishly or unnecessarily; do it because it makes sense to do it".

    >>>> Some of the rest of you may differ dramatically on what should be
    >>>> logged and when it is okay not to bother. I hope some of you can
    >>>> share those thoughts with me.
    >>>>
    >>>> Basically, I'm just about ready to start getting loggers for each
    >>>> and every class in the project I'm working on now (with plans to do
    >>>> the same in every project as I create it or return to it). But I
    >>>> don't want to do too much logging either.....
    >>>
    >>> You should log the information you expect potentially could be
    >>> useful when troubleshooting a problem.

    >
    > This requires that you think like a useful person, not a computer
    > programmer.
    >
    > When you're troubleshooting a log, you don't have code in front of
    > you. You have what the log tells you. It had better God-damned tell
    > you what you need, because you wouldn't be looking if someone weren't
    > breathing down your neck. No fancy
    > "***********************=============" strings. Logs are dense,
    > multi-mega- or gigabyte beasts of tightly printed strings.
    >
    > Ops personnel read logs. Ops personnel think programmers are children.
    > I had an ops mentor who told me, "We love getting the programmers from
    > [the development location] here for six months. They go back to coding
    > _changed_!"
    >
    > Other times they're cursing the programmers who wrote such lame
    > logging statements.
    >

    I have had very little contact with operators in the PC era but I did
    have some in my mainframe days. Back then, they didn't have a lot to do
    with fixing the problems in the sense of repairing the code. Their job
    was basically to figure out which program had bombed and then look that
    up in their list so they knew which programmer to call.

    Would I be right in assuming that it's pretty much the same situation
    today in a massively PC-oriented world? Or have they assumed many new
    responsibilities?

    It would help a lot to know what they hope to find in a log.

    If they still operate like they did in my mainframe days, I expect they
    pretty much just want to know which program failed so they can look up
    the on-call programmer's name. They won't care about most details,
    although they might like to be able to tell the programmer "Program Foo
    crashed on an IllegalArgumentException in the constructor for class
    FooMainPanel" as opposed to just "Program Foo crashed". They won't care
    about stacktraces or such things. But the programmer is going to care a
    lot about the stacktraces and other information!

    But maybe modern operators do a lot more than that. You seem very
    familiar with what they do so this would be a great chance to get your
    insight on this.


    >>> And as a general rule, then if any doubt then log, because it
    >>> is usually better to have too much logging than too little
    >>> logging.
    >>>
    >>> I do not see any need for logging in an enum or in a pure
    >>> data class (holder class).

    >
    > Some enums.
    >
    > Like any other class, it depends on what it does. But generally you
    > log state changes, i.e., behavioral methods (not usually attributes).
    > You log anything that is weird. You log errors and warnings.
    >
    > You pick appropriate logging levels. Here's my log4j idiom:
    >
    > public void loadResource()
    > {
    > logger.debug("");
    >
    > final BufferedReader reader;
    > try
    > {
    > reader = new BufferedReader(new InputStreamReader(getClass()
    > .getResourceAsStream("/config/configuration.txt")));
    > }
    > catch(IOException exc)
    > {
    > String msg = "Cannot open configuration. "+
    > exc.getLocalizedMessage(); logger.error(msg, exc);
    > throw new IllegalStateException(msg, exc);
    > }
    > assert reader != null;
    >
    > try
    > {
    > // read the Reader, etc.
    > }
    > catch(IOException exc)
    > {
    > String msg = "Cannot read configuration. "+
    > exc.getLocalizedMessage(); logger.error(msg, exc);
    > throw new IllegalStateException(msg, exc);
    > }
    > finally
    > {
    > try
    > {
    > reader.close();
    > }
    > catch(IOException exc)
    > {
    > String msg = "Cannot close configuration. "
    > + exc.getLocalizedMessage();
    > logger.warn(msg, exc);
    > }
    > }
    > }
    >
    > Note the multiple uses of 'logger' (an instance member) in that
    > method.
    >

    Interesting. I'm going to need to imitate that...

    >>> But please add a toString method in your data class, so
    >>> when the class with real login in that uses the data class
    >>> can log it and you get something useful in the log about the
    >>> data.
    >>>

    >> Sorry, I'm not following you.
    >>
    >> Are you saying that the toString() method needs to be there to turn
    >> things like references into meaningful information? I know that a
    >> reference to something like a JFrame is not going to be very
    >> meaningful and would rather display the name given the JFrame via
    >> setName(). Or are you saying something quite different?

    >
    > 'toString()' should always give a useful way to identify the specific
    > instance.
    >

    That's why I was displaying the name of the table so I'm going to take
    that as a "yes" ;-)

    > It should depend on (and usually only on) the same fields used to
    > drive 'hashCode()' and 'equals()' and if supported, 'compareTo()'
    > (which should always be consistent with each other).
    >

    I don't touch hashCode(), equals() or compareTo() very often at all. Or
    toString() either for that matter. But overriding hashCode() and equals()
    solved a big problem for me recently. I was writing a holder class that
    contained three fields, a table name, a row number and a column number,
    as a key for a hash map and then storing the column width in the value
    portion of the map. But when I tried to look up a given combination of
    table name, row number and column number I was never finding values that
    I knew were there. I googled and found out that I needed to revise
    hashCode() and equals() and rougly what those changes needed to be and
    then my lookups went fine. But I didn't touch compareTo(). Hmm, maybe I
    need to revisit that and make sure it doesn't need some tweaking too....

    --
    Novice
     
    Novice, Feb 27, 2012
    #5
  6. Novice

    Lew Guest

    On 02/27/2012 11:14 AM, Novice wrote:
    > I don't touch hashCode(), equals() or compareTo() very often at all. Or
    > toString() either for that matter. But overriding hashCode() and equals()
    > solved a big problem for me recently. I was writing a holder class that
    > contained three fields, a table name, a row number and a column number,
    > as a key for a hash map and then storing the column width in the value
    > portion of the map. But when I tried to look up a given combination of
    > table name, row number and column number I was never finding values that
    > I knew were there. I googled and found out that I needed to revise
    > hashCode() and equals() and rougly what those changes needed to be and
    > then my lookups went fine. But I didn't touch compareTo(). Hmm, maybe I
    > need to revisit that and make sure it doesn't need some tweaking too....


    Read the Javadocs for those methods.

    'compareTo()' is not universal, being present only in instances of
    'Comparable<T>'.

    --
    Lew
    Honi soit qui mal y pense.
    http://upload.wikimedia.org/wikipedia/commons/c/cf/Friz.jpg
     
    Lew, Feb 27, 2012
    #6
  7. Novice

    Daniel Pitts Guest

    On 2/26/12 6:02 PM, Novice wrote:
    > I _think_ these supplementary questions will be a lot less of a struggle
    > than the others in "Aspect questions" thread. That thread is getting on
    > the long side so I thought I'd ask these in a new thread.
    >
    > Basically, I'm looking for advice on what should always be logged by
    > every class. I understand now that every class is going to have its own
    > logger but what should be logged?
    >
    > Or to put it another way, are there cases where a class won't log at all?


    Classes don't log, methods log. The question then becomes, which methods
    should (or shouldn't) log.

    What I've determined is that you need to ask yourself a series of questions.

    Would logging at this point be useful at all?
    Who would it be useful for? Developers, Operations, and/or business?
    How much information should I include? Just a message stating an event
    happened? Metrics about that event? Details about all objects involved?
    What log level should I use?

    For example, if you think a Developer needs this information, but no one
    else does, then a "debug" level log with all relevant details makes
    sense. Operations and/or business probably just want metrics.
    Operations just want errors or severe warnings.

    Also, depending on where the code runs, you may enable/disable certain
    levels of logging. Production probably only warnings/errors should be
    enabled after start up, and info should be enabled only during start-up.
    In UAT/QA, more information seems useful. Enabling debug makes sense in
    DEV environments, *or* when you have a problem you can't reproduce in a
    dev environment.

    It is also possible (though rare) to have a class have multiple loggers.
    I can think of exactly one use-case, but there may be more. That
    use-case is specifically for metric logging. If you have some metrics
    your code collects to log, then it might make sense to separate those
    out into there own "log stream".

    Hope this helps,
    Daniel.
     
    Daniel Pitts, Feb 27, 2012
    #7
  8. Novice

    Lew Guest

    Novice wrote:
    > Lew wrote:
    >> 'toString()' should always give a useful way to identify the specific
    >> instance.
    >>

    > That's why I was displaying the name of the table so I'm going to take
    > that as a "yes" ;-)
    >
    >> It should depend on (and usually only on) the same fields used to
    >> drive 'hashCode()' and 'equals()' and if supported, 'compareTo()'
    >> (which should always be consistent with each other).
    >>

    > I don't touch hashCode(), equals() or compareTo() very often at all. Or
    > toString() either for that matter. But overriding hashCode() and equals()
    > solved a big problem for me recently. I was writing a holder class that
    > contained three fields, a table name, a row number and a column number,
    > as a key for a hash map and then storing the column width in the value
    > portion of the map. But when I tried to look up a given combination of
    > table name, row number and column number I was never finding values that
    > I knew were there. I googled and found out that I needed to revise
    > hashCode() and equals() and rougly what those changes needed to be and
    > then my lookups went fine. But I didn't touch compareTo(). Hmm, maybe I
    > need to revisit that and make sure it doesn't need some tweaking too....


    The default 'Object#equals()' method gives (almost) the same answer as the ==
    operator - two instances are equal iff (if and only if) they are the same
    instance. That's called "object equality" or "instance equality".

    Often we want value equality. It's not very helpful, for example, for

    new Integer(67551) != new Integer(67551)

    to be true.

    That means equality of different instances, provided their values match in
    some defined way.

    For 'Integer', value equality depends on only one attribute, the underlying
    'int' field.

    For a more complicated type, there might be more than one attribute. For
    example, you might regard two widgets as equal iff they have the same owner
    and color:

    public class Widget
    {
    private final Owner owner; // assume getter methods for brevity
    private final Color color;
    public Widget(Owner owner, Color color)
    {
    if (owner == null || color == null)
    {
    throw new IllegalArgumentException("null");
    }
    this.owner = owner;
    this.color = color'
    assert this.owner != null && this.color != null;
    }
    @Override public boolean equals(Object other)
    {
    if (other == this) { return true; }
    if (! (other instanceof Widget)) { return false; }
    Widget widget = (Widget) other;
    return this.getOwner().equals(widget.getOwner())
    && this.getColor().equals(widget.getColor()
    }
    }

    But wait! 'hashCode()', which is a shortcut for 'equals()' (well, really for
    "not equals()"), doesn't know anything about those fields yet! It will think
    objects are not equal when they really are.

    So you have to override 'hashCode()', too, using the same fields, so that its
    result are consistent with 'equals()':

    @Override public int hashCode()
    {
    return owner.hashCode() * 31 + color.hashCode();
    }

    Since 'compareTo()' also makes claims about equality, it must agree with the
    other two methods when it exists.

    Since the fields that determine equality identify the value by definition,
    they should determine the 'toString()' result also:

    @Override public String toString()
    {
    assert owner != null && color != null;
    return '{'+ owner +", "+ color + '}';
    }

    As you can see, 'toString()' can include additional information, but it must
    show what identifies the value.

    --
    Lew
    Honi soit qui mal y pense.
    http://upload.wikimedia.org/wikipedia/commons/c/cf/Friz.jpg
     
    Lew, Feb 27, 2012
    #8
  9. Novice

    Arne Vajhøj Guest

    On 2/26/2012 11:12 PM, Novice wrote:
    > Arne Vajhøj<> wrote in
    >> But please add a toString method in your data class, so
    >> when the class with real login in that uses the data class
    >> can log it and you get something useful in the log about the
    >> data.
    >>

    > Sorry, I'm not following you.
    >
    > Are you saying that the toString() method needs to be there to turn
    > things like references into meaningful information? I know that a
    > reference to something like a JFrame is not going to be very meaningful
    > and would rather display the name given the JFrame via setName(). Or are
    > you saying something quite different?


    I am saying that.

    Object toString is not very useful so you want to
    override with something that returns relevant information.

    Arne
     
    Arne Vajhøj, Feb 28, 2012
    #9
  10. Novice

    Novice Guest

    Daniel Pitts <> wrote in
    news:GPR2r.17474$:

    > On 2/26/12 6:02 PM, Novice wrote:
    >> I _think_ these supplementary questions will be a lot less of a
    >> struggle than the others in "Aspect questions" thread. That thread is
    >> getting on the long side so I thought I'd ask these in a new thread.
    >>
    >> Basically, I'm looking for advice on what should always be logged by
    >> every class. I understand now that every class is going to have its
    >> own logger but what should be logged?
    >>
    >> Or to put it another way, are there cases where a class won't log at
    >> all?

    >
    > Classes don't log, methods log.


    You're right of course. I was just shorthanding by talking about classes
    logging. :)

    >The question then becomes, which
    > methods should (or shouldn't) log.
    >
    > What I've determined is that you need to ask yourself a series of
    > questions.
    >
    > Would logging at this point be useful at all?
    > Who would it be useful for? Developers, Operations, and/or business?
    > How much information should I include? Just a message stating an event
    > happened? Metrics about that event? Details about all objects
    > involved? What log level should I use?
    >
    > For example, if you think a Developer needs this information, but no
    > one else does, then a "debug" level log with all relevant details
    > makes sense. Operations and/or business probably just want metrics.
    > Operations just want errors or severe warnings.
    >
    > Also, depending on where the code runs, you may enable/disable certain
    > levels of logging. Production probably only warnings/errors should be
    > enabled after start up, and info should be enabled only during
    > start-up. In UAT/QA, more information seems useful. Enabling debug
    > makes sense in DEV environments, *or* when you have a problem you
    > can't reproduce in a dev environment.
    >
    > It is also possible (though rare) to have a class have multiple
    > loggers.
    > I can think of exactly one use-case, but there may be more. That
    > use-case is specifically for metric logging. If you have some metrics
    > your code collects to log, then it might make sense to separate those
    > out into there own "log stream".
    >
    > Hope this helps,


    It does! Thanks Daniel!

    --
    Novice
     
    Novice, Feb 28, 2012
    #10
  11. On 2/27/2012 1:13 AM, Lew wrote:
    > Novice wrote:
    >> Arne Vajhøj wrote:
    >>> Novice wrote:
    >>>> Basically, I'm just about ready to start getting loggers for each and
    >>>> every class in the project I'm working on now (with plans to do the
    >>>> same in every project as I create it or return to it). But I don't
    >>>> want to do too much logging either.....
    >>>
    >>> You should log the information you expect potentially could be
    >>> useful when troubleshooting a problem.

    >
    > This requires that you think like a useful person, not a computer
    > programmer.
    >
    > When you're troubleshooting a log, you don't have code in front of you.
    > You have what the log tells you. It had better God-damned tell you what
    > you need, because you wouldn't be looking if someone weren't breathing
    > down your neck. No fancy "***********************=============" strings.
    > Logs are dense, multi-mega- or gigabyte beasts of tightly printed strings.
    >
    > Ops personnel read logs. Ops personnel think programmers are children. I
    > had an ops mentor who told me, "We love getting the programmers from
    > [the development location] here for six months. They go back to coding
    > _changed_!"


    Warning and above may be ops relevant, but the lower levels and
    that is typical the majority of log messages are for developers.

    > You pick appropriate logging levels. Here's my log4j idiom:
    >
    > public void loadResource()
    > {
    > logger.debug("");
    >
    > final BufferedReader reader;
    > try
    > {
    > reader = new BufferedReader(new InputStreamReader(getClass()
    > .getResourceAsStream("/config/configuration.txt")));
    > }
    > catch(IOException exc)
    > {
    > String msg = "Cannot open configuration. "+ exc.getLocalizedMessage();
    > logger.error(msg, exc);
    > throw new IllegalStateException(msg, exc);
    > }
    > assert reader != null;
    >
    > try
    > {
    > // read the Reader, etc.
    > }
    > catch(IOException exc)
    > {
    > String msg = "Cannot read configuration. "+ exc.getLocalizedMessage();
    > logger.error(msg, exc);
    > throw new IllegalStateException(msg, exc);
    > }
    > finally
    > {
    > try
    > {
    > reader.close();
    > }
    > catch(IOException exc)
    > {
    > String msg = "Cannot close configuration. "
    > + exc.getLocalizedMessage();
    > logger.warn(msg, exc);
    > }
    > }
    > }
    >
    > Note the multiple uses of 'logger' (an instance member) in that method.


    Why do you combine English with local language?

    >>> But please add a toString method in your data class, so
    >>> when the class with real login in that uses the data class
    >>> can log it and you get something useful in the log about the
    >>> data.
    >>>

    >> Sorry, I'm not following you.
    >>
    >> Are you saying that the toString() method needs to be there to turn
    >> things like references into meaningful information? I know that a
    >> reference to something like a JFrame is not going to be very meaningful
    >> and would rather display the name given the JFrame via setName(). Or are
    >> you saying something quite different?

    >
    > 'toString()' should always give a useful way to identify the specific
    > instance.
    >
    > It should depend on (and usually only on) the same fields used to drive
    > 'hashCode()' and 'equals()' and if supported, 'compareTo()' (which
    > should always be consistent with each other).


    I would usually expect more fields than the identity fields
    to be useful.

    Arne
     
    Arne Vajhøj, Feb 28, 2012
    #11
  12. Novice

    Novice Guest

    Lew <> wrote in news:jigr8v$jhh$:

    > Novice wrote:
    >> Lew wrote:
    >>> 'toString()' should always give a useful way to identify the
    >>> specific instance.
    >>>

    >> That's why I was displaying the name of the table so I'm going to
    >> take that as a "yes" ;-)
    >>
    >>> It should depend on (and usually only on) the same fields used to
    >>> drive 'hashCode()' and 'equals()' and if supported, 'compareTo()'
    >>> (which should always be consistent with each other).
    >>>

    >> I don't touch hashCode(), equals() or compareTo() very often at all.
    >> Or toString() either for that matter. But overriding hashCode() and
    >> equals() solved a big problem for me recently. I was writing a holder
    >> class that contained three fields, a table name, a row number and a
    >> column number, as a key for a hash map and then storing the column
    >> width in the value portion of the map. But when I tried to look up a
    >> given combination of table name, row number and column number I was
    >> never finding values that I knew were there. I googled and found out
    >> that I needed to revise hashCode() and equals() and rougly what those
    >> changes needed to be and then my lookups went fine. But I didn't
    >> touch compareTo(). Hmm, maybe I need to revisit that and make sure it
    >> doesn't need some tweaking too....

    >
    > The default 'Object#equals()' method gives (almost) the same answer as
    > the == operator - two instances are equal iff (if and only if) they
    > are the same instance. That's called "object equality" or "instance
    > equality".
    >
    > Often we want value equality. It's not very helpful, for example, for
    >
    > new Integer(67551) != new Integer(67551)
    >
    > to be true.
    >
    > That means equality of different instances, provided their values
    > match in some defined way.
    >
    > For 'Integer', value equality depends on only one attribute, the
    > underlying 'int' field.
    >
    > For a more complicated type, there might be more than one attribute.
    > For example, you might regard two widgets as equal iff they have the
    > same owner and color:
    >
    > public class Widget
    > {
    > private final Owner owner; // assume getter methods for brevity
    > private final Color color;
    > public Widget(Owner owner, Color color)
    > {
    > if (owner == null || color == null)
    > {
    > throw new IllegalArgumentException("null");
    > }
    > this.owner = owner;
    > this.color = color'
    > assert this.owner != null && this.color != null;
    > }
    > @Override public boolean equals(Object other)
    > {
    > if (other == this) { return true; }
    > if (! (other instanceof Widget)) { return false; }
    > Widget widget = (Widget) other;
    > return this.getOwner().equals(widget.getOwner())
    > && this.getColor().equals(widget.getColor()
    > }
    > }
    >
    > But wait! 'hashCode()', which is a shortcut for 'equals()' (well,
    > really for "not equals()"), doesn't know anything about those fields
    > yet! It will think objects are not equal when they really are.
    >
    > So you have to override 'hashCode()', too, using the same fields, so
    > that its result are consistent with 'equals()':
    >
    > @Override public int hashCode()
    > {
    > return owner.hashCode() * 31 + color.hashCode();
    > }
    >

    Just curious: why multiply the owner.hashCode() by 31? There's no
    significance to the 31 is there? Why not just add the two hashCodes
    together without multiplying one of them first? That would be just as
    good, right?

    > Since 'compareTo()' also makes claims about equality, it must agree
    > with the other two methods when it exists.
    >
    > Since the fields that determine equality identify the value by
    > definition, they should determine the 'toString()' result also:
    >
    > @Override public String toString()
    > {
    > assert owner != null && color != null;
    > return '{'+ owner +", "+ color + '}';
    > }
    >
    > As you can see, 'toString()' can include additional information, but
    > it must show what identifies the value.
    >

    That makes perfect sense to me!


    --
    Novice
     
    Novice, Feb 28, 2012
    #12
  13. Novice

    Arne Vajhøj Guest

    On 2/27/2012 2:14 PM, Novice wrote:
    > Lew<> wrote in news:jif6ua$3cm$:
    >
    >> Novice wrote:
    >>> Arne Vajhøj wrote:
    >>>> Novice wrote:
    >>>>> Some of the rest of you may differ dramatically on what should be
    >>>>> logged and when it is okay not to bother. I hope some of you can
    >>>>> share those thoughts with me.
    >>>>>
    >>>>> Basically, I'm just about ready to start getting loggers for each
    >>>>> and every class in the project I'm working on now (with plans to do
    >>>>> the same in every project as I create it or return to it). But I
    >>>>> don't want to do too much logging either.....
    >>>>
    >>>> You should log the information you expect potentially could be
    >>>> useful when troubleshooting a problem.

    >>
    >> This requires that you think like a useful person, not a computer
    >> programmer.
    >>
    >> When you're troubleshooting a log, you don't have code in front of
    >> you. You have what the log tells you. It had better God-damned tell
    >> you what you need, because you wouldn't be looking if someone weren't
    >> breathing down your neck. No fancy
    >> "***********************=============" strings. Logs are dense,
    >> multi-mega- or gigabyte beasts of tightly printed strings.
    >>
    >> Ops personnel read logs. Ops personnel think programmers are children.
    >> I had an ops mentor who told me, "We love getting the programmers from
    >> [the development location] here for six months. They go back to coding
    >> _changed_!"
    >>
    >> Other times they're cursing the programmers who wrote such lame
    >> logging statements.
    >>

    > I have had very little contact with operators in the PC era but I did
    > have some in my mainframe days. Back then, they didn't have a lot to do
    > with fixing the problems in the sense of repairing the code. Their job
    > was basically to figure out which program had bombed and then look that
    > up in their list so they knew which programmer to call.
    >
    > Would I be right in assuming that it's pretty much the same situation
    > today in a massively PC-oriented world? Or have they assumed many new
    > responsibilities?
    >
    > It would help a lot to know what they hope to find in a log.
    >
    > If they still operate like they did in my mainframe days, I expect they
    > pretty much just want to know which program failed so they can look up
    > the on-call programmer's name. They won't care about most details,
    > although they might like to be able to tell the programmer "Program Foo
    > crashed on an IllegalArgumentException in the constructor for class
    > FooMainPanel" as opposed to just "Program Foo crashed". They won't care
    > about stacktraces or such things. But the programmer is going to care a
    > lot about the stacktraces and other information!
    >
    > But maybe modern operators do a lot more than that. You seem very
    > familiar with what they do so this would be a great chance to get your
    > insight on this.


    PC's as such does not have operator operators.

    But various Unix, Linux and Windows servers have.

    How much ops troubleshoot before calling in software
    varies between companies.

    Some types of problems can be fixed by ops.

    But if the problem is really a software bug, then
    ops can not do much.

    Arne
     
    Arne Vajhøj, Feb 28, 2012
    #13
  14. Novice

    Arne Vajhøj Guest

    On 2/27/2012 8:48 PM, Novice wrote:
    > Lew<> wrote in news:jigr8v$jhh$:
    >> But wait! 'hashCode()', which is a shortcut for 'equals()' (well,
    >> really for "not equals()"), doesn't know anything about those fields
    >> yet! It will think objects are not equal when they really are.
    >>
    >> So you have to override 'hashCode()', too, using the same fields, so
    >> that its result are consistent with 'equals()':
    >>
    >> @Override public int hashCode()
    >> {
    >> return owner.hashCode() * 31 + color.hashCode();
    >> }
    >>

    > Just curious: why multiply the owner.hashCode() by 31? There's no
    > significance to the 31 is there? Why not just add the two hashCodes
    > together without multiplying one of them first? That would be just as
    > good, right?


    Actually not.

    Some numbers are better than others (no multiply means 1).

    You should go for a prime.

    31 is often used.

    But there are other good numbers.

    Arne
     
    Arne Vajhøj, Feb 28, 2012
    #14
  15. Novice

    Lew Guest

    On 02/27/2012 05:57 PM, Arne Vajhøj wrote:
    > On 2/27/2012 8:48 PM, Novice wrote:
    >> Lew<> wrote in news:jigr8v$jhh$:
    >>> But wait! 'hashCode()', which is a shortcut for 'equals()' (well,
    >>> really for "not equals()"), doesn't know anything about those fields
    >>> yet! It will think objects are not equal when they really are.
    >>>
    >>> So you have to override 'hashCode()', too, using the same fields, so
    >>> that its result are consistent with 'equals()':
    >>>
    >>> @Override public int hashCode()
    >>> {
    >>> return owner.hashCode() * 31 + color.hashCode();
    >>> }
    >>>

    >> Just curious: why multiply the owner.hashCode() by 31? There's no
    >> significance to the 31 is there? Why not just add the two hashCodes
    >> together without multiplying one of them first? That would be just as
    >> good, right?

    >
    > Actually not.
    >
    > Some numbers are better than others (no multiply means 1).
    >
    > You should go for a prime.
    >
    > 31 is often used.
    >
    > But there are other good numbers.


    Donald Knuth, _The Art of Computer Programming_, suggested 31 for modulo 32
    hashes because it produces good distribution on the average for random inputs.

    There are better hashes, but for most purposes the one illustrated suffices.

    More generally (pseudocode):

    @Override int hashCode()
    {
    int hash = 17; // or 0 or some other decent starter
    for (Object field : instanceFields)
    {
    hash = hash * 31;
    hash += field.hashCode();
    }
    return hash;
    }

    I skipped over niceties like whether to use 0 for null or empty element hashes.

    The Javadocs explain some of this, but the purpose of 'hashCode()' mostly is
    to quickly tell if instances are not equal. Equal instances must have the same
    hash - that's a hard rule. But the converse cannot apply - many instances will
    have the same hash even if they're not equal.

    The trick is not to have many all at once during the run of your application.

    Then probability is your friend. With 2^^32 possible hashcodes, let's say for
    a map of 'String' to 'Foo', you have a lot of room for many strings before you
    get too many collisions. That means you need a hash that is about equally
    likely to pick any number as any other, so that for any given set of actual
    values at a time you probably will have different hashes for different objects.

    In practice you get a few collisions, but as long as "a few" is within
    acceptable limits you're good to go. Otherwise you have to play advanced
    reindeer games with hash algorithms.

    Why all this hash fooferol? Because once you have a hash value lookups are so
    much faster on 'int' comparisons than between arbitrary 'String's of varying
    length. So let's say you're trying to find an element in a 'Set' - you look
    for the hashcode first, and if you find zero or one element you're done.
    Otherwise you distinguish between the colliding elements with 'equals()', but
    hopefully only for two or at worst three elements.

    Hashes are also useful in cryptography.

    It's an endless topic, full of fun. You need never be bored of a winter's eve.
    I'd actually start with Wikipedia on this one.

    Aside: I seem to recall an 'equals()' method builder in Apache Commons Lang
    somewhere. It's got some sort of indicative name like 'EqualsBuilder'.

    --
    Lew
    Honi soit qui mal y pense.
    http://upload.wikimedia.org/wikipedia/commons/c/cf/Friz.jpg
     
    Lew, Feb 28, 2012
    #15
  16. Novice

    Lew Guest

    Arne Vajhøj wrote:
    > Novice wrote:
    >> But maybe modern operators do a lot more than that. You seem very
    >> familiar with what they do so this would be a great chance to get your
    >> insight on this.

    >
    > PC's as such does not have operator operators.
    >
    > But various Unix, Linux and Windows servers have.
    >
    > How much ops troubleshoot before calling in software
    > varies between companies.
    >
    > Some types of problems can be fixed by ops.
    >
    > But if the problem is really a software bug, then
    > ops can not do much.


    Sure they can, in partnership with a developer. Someone has to reconfigure
    logs to tighter levels (such as DEBUG), restart servers, instantiate test
    modes, make logs available to the developer, etc.

    As with what you said, this varies between companies.

    --
    Lew
    Honi soit qui mal y pense.
    http://upload.wikimedia.org/wikipedia/commons/c/cf/Friz.jpg
     
    Lew, Feb 28, 2012
    #16
  17. On 2/28/2012 2:52 AM, Leif Roar Moldskred wrote:
    > Arne Vajhøj<> wrote:
    >
    >> Warning and above may be ops relevant, but the lower levels and
    >> that is typical the majority of log messages are for developers.

    >
    > That's one idea I miss in the Java logging idiom: that there are
    > different types of logs that are kept for different purposes and that
    > a log message might be relevant for only one or some of these log
    > types.
    >
    > That the system can't create a temporary file because there's not
    > enough available disk space is of vital importance in an operations
    > log, but completely irrelevant to an audit-trail log and of lesser
    > importance to an error-tracking / debugging support log.
    >
    > While it's trivial to implement logging to do this, as the idea hasn't
    > taken hold in the Java culture it's hardly ever done, which is a pity.


    I think the split out of ops relevant data is typical done via the
    ops monitoring software (IBM Tivoli, CA Unicenter or whatever).

    Arne
     
    Arne Vajhøj, Feb 28, 2012
    #17
  18. On 12-02-28 03:52 AM, Leif Roar Moldskred wrote:
    > Arne Vajhøj <> wrote:
    >
    >> Warning and above may be ops relevant, but the lower levels and
    >> that is typical the majority of log messages are for developers.

    >
    > That's one idea I miss in the Java logging idiom: that there are
    > different types of logs that are kept for different purposes and that
    > a log message might be relevant for only one or some of these log
    > types.
    >
    > That the system can't create a temporary file because there's not
    > enough available disk space is of vital importance in an operations
    > log, but completely irrelevant to an audit-trail log and of lesser
    > importance to an error-tracking / debugging support log.
    >
    > While it's trivial to implement logging to do this, as the idea hasn't
    > taken hold in the Java culture it's hardly ever done, which is a pity.
    >

    You're correct on all counts. It _is_ relatively trivial to implement
    logging to do this: I worked on logging improvements to several
    good-sized apps for a client some years back, and one of the main
    features was to build extra classifiers into the formatted log messages.
    That way someone could relatively easily Splunk or grep on log entries
    that applied to a given subsystem or subsystems.

    I suspect one of the main reasons why you don't see more developers do
    this is because they get so used to being spoonfed by feature-rich
    frameworks that they don't fully realize that there are still big gaps
    that they can and should fill. Of all the conversion specifiers provided
    by log4j for PatternLayout, for example, it's that innocuous little %m
    which is most often not taken advantage of. It's up to the logging
    designer to mandate extra formatting information there, such as user IDs
    or functional classifiers, to go along with event-specific messages.

    I've seen - more often than I care to remember - web app logging that
    was unable to support pulling out just the log entries that pertained to
    a given authenticated user. Other logs I've seen have no provision to
    write out the stack trace of an exception (log4j actually supports this,
    but it's amazing how many folks miss that in the API). It makes a person
    wonder why people even bother writing to logs.

    AHS

    --
    -- Gaiety is the most outstanding feature of the Soviet Union.
    Josef Stalin, November 1935
     
    Arved Sandstrom, Feb 29, 2012
    #18
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Mark
    Replies:
    2
    Views:
    686
    Steven Cheng[MSFT]
    Jan 28, 2004
  2. Stefan Siegl
    Replies:
    0
    Views:
    991
    Stefan Siegl
    Aug 27, 2003
  3. janne
    Replies:
    0
    Views:
    9,632
    janne
    Sep 10, 2004
  4. Christoph Haas
    Replies:
    0
    Views:
    484
    Christoph Haas
    Jun 12, 2006
  5. Randy Kramer

    TkHTML progress, additional questions

    Randy Kramer, Apr 3, 2006, in forum: Ruby
    Replies:
    0
    Views:
    119
    Randy Kramer
    Apr 3, 2006
Loading...

Share This Page