Overview

This page describes the intended implementation of error management within a FileMaker solution. This page outlines a method of error handling which extends far beyond using FileMaker's own built-in Get ( LastError ) function. Your FileMaker solution can potentially experience far more errors than just those generated by FileMaker itself.

The temporary GitHub repository contains the custom functions mentioned on this page, and a sample FileMaker 12 file. Once the functions are complete, they will be moved to the main fmpstandards repository.

Error Origination

There are many places where an error can occur within a FileMaker solution. It doesn't stop with just FileMaker the application. The most fundamental errors within FileMaker are obviously those generated by FileMaker itself. These FileMaker error codes could be handled with FileMaker's own Get ( LastError ) function. However, there are many external technologies (such as plugins), as well as the logic used to create your FileMaker solution which can also generate errors. We refer to the origin of an error as an error type. The following items are the suggested classifications for error types.

typeexplanation of this type
Fmp

FileMaker error code

App

An error which the developer generates within the logic of your own solution. An example of this would be a Perform script step higher in a sequence of steps which is required in order for subsequent Perform script steps to function properly.

Plugin: NamePlug-in specific error, specify the name of the plugin such as "Plugin: ScriptMaster", "Plugin: BaseElements", "Plugin: TroiDialog", etc.
Function: NameCustom function specific error. Specify the name of the function. When saving error data in a custom function, use the reserved variable $functionError.
other

Any error source can have it's own error type associated with it; here are some examples:

  • if you are interacting with a SOAP web service, the error type could be the name of the web service.
  • if writing a mFM Module, the error type could be the name of the module, like: "mFM: JSON"

Error Storage

Because any error may have many different causal facets, such as relevant associated environmental information at the time the error occurred, a predefined number of error attributes is unrealistic. One of the primary goals is to capture environment specific information at the time of the error. Due to the amount of possible supporting information around any given error, all information relating to the error should be saved to the reserved variable $error. This variable contains name/value pairs, encoded by the # ( name ; value ) function and accessible by the #Get and #Assign functions.

example of $error variable contents
$errorType = "Fmp";
$errorCode = GetAsNumber ( 105 ) ;
$errorDescription = "Layout is missing";
$errorInfo = "Go to some layout";
$AllowAbortState = GetAsNumber ( 1 );
$CurrentHostTimestamp = GetAsTimestamp ( "11/15/2013 5:36:12 PM" );
$ErrorCaptureState = GetAsNumber ( 0 );
$LastODBCError = "";
$ScriptName = "Run Error Examples";
$ScriptParameter = "";
$ScriptResult = "";
$UserCount = GetAsNumber ( 1 );

Custom Functions

Using a unique custom function for every unique source of an error provides a powerful method of capturing all necessary data about an error in a standardized format. All error specific functions are prefixed with the reserved domain of "Error".

Error ( theErrorType ; theErrorCode ; theErrorDescription ; theErrorInfo )

Return Let Notation containing information about the error and the environment it occurred in.

This function contains a recommended set of environmental data, but you may choose to add or remove name/value pairs from this function as you see fit for your solution. All error type specific custom functions call this function, so it is the central place to define default environmental data collected when an error occurs.

ErrorFmp ( theErrorCode ; theErrorInfo )

Return Let Notation containing information about a FileMaker error and the environment it occurred in.

ErrorFmpGetLast ( theErrorInfo )

Return Let Notation containing information about the last FileMaker error and the environment it occurred in.

Produces the same result as ErrorFmp ( Get ( LastError ) ; "info about where the error occurred" ), but does not depend on ErrorFmp function.

ErrorFmp vs ErrorFmpGetLast

Typically, when evaluating a FileMaker error, you want to evaluate the last error that occurred, so you would use ErrorFmpGetLast.

In certain circumstances* however, this is not sufficient, so I have also created ErrorFmp.

* when using EvaluationError ( expression ) function, or when the result of Get ( LastError ) has been saved to a variable

ErrorApp ( theErrorCode ; theErrorInfo )

Return Let Notation containing information about the specified solution-specific error and the environment it occurred in.

This function should be modified by each developer/solution to map your own error codes to error descriptions.

ErrorFound ( theErrorData )

Return errorCode contained in theErrorData as a Boolean. In other words, if any error occurred at all, return True (1), otherwise return False (0).

Since $error is the reserved variable name for storing error data, this function will almost always be uses like this: ErrorFound ( $error )

Error[SOURCE] ( any ; parameters ; necessary ; theErrorInfo )

Where SOURCE is unique to the origination of the error and is the same as/similar to errorType. Examples of plugin error functions would be ErrorPlugInScriptMaster or ErrorPlugInTroiDialog. The code repository contains some examples of this type of function. The intention of these functions are to translate the error information provided by the SOURCE into the standardized format implemented by these custom functions.

Over time, it would be nice to see the community and plugin developers contribute error handling custom functions for various plugins and services.

Using the Custom Functions

ErrorFmpGetLast
Go to Layout [Client: List]
Set Variable [$error; Value:ErrorFmpGetLast ( "go to Client: List layout" )]

Additional name/value pairs can be added to the $error variable using the # ( name ; value ) function:

ErrorFmpGetLast with additional data
Set Field By Name [$fieldName; $value]
Set Variable [$error; Value:ErrorFmpGetLast ( "go to Client: List layout" ) & # ( "fieldName" ; $fieldName ) & # ( "value" ; $value )]
ErrorApp
If [Get ( FoundCount ) = 0]
	Set Variable [$error; Value:ErrorApp ( 4 ; "validation section at start of script" )]
End If
# the parameter 4 is an example only, the code and description for the code must be defined in the ErrorApp custom function
ErrorFound
If [ErrorFound ( $error )]
	# log the error
	Exit Script [Result: # ( "error" ; $error )]
End If
  • No labels

126 Comments

  1. Diagnostic information more detailed than the error code is of course a handy thing to have. This looks like a variation on the idea behind Matt's Error* functions, which is to create a data structure to mimic the role of thrown exception objects in other programming languages. I'm reluctant to call that structure an "object" for fear of confusing developers more familiar with some object oriented language than with FileMaker: the resulting object has no methods (i.e. we can't pass it a message).

    I'm not sure whether I like the independence from a solution's dictionary functions or not. Ordinarily, I like orthogonal functionality. But as far as I can discern, an error structure is just a dictionary that we can trust to have values for particular names, and the ErrorObject function is a shortcut for generating it. Correct me if I'm misunderstanding. We may as well call a spade a spade.

    For my own part, I've been reluctant to use Matt's Error* functions because they usually strike me as being too "heavy" for what I usually find myself doing with error handling. A script that encounters an error and needs to decide what to do next needs far less information than a developer diagnosing a bug after the fact (especially if the developer doesn't have access to the environment where the error happened). The former is a larger concern for me in most of my work, but I know this isn't true for everyone. Perhaps a two-pronged approach is in order with a minimalist error code for programatic handling of errors (which seems to follow FileMaker's lead) and a separate error log written to a global variable or records in a log table for diagnosis by a developer? Having a log to reference is a particularly appealing idea to me because we can see not only the state of several parameters at the time of the error, but also the events leading up to the error.

    1. Yes, it is a variation of Matt's Error* functions. I chose the name "object" after reading on json.org that "An object is an unordered set of name/value pairs", which seemed like the perfect fit. Although, as you pointed out, an "object" in other programming languages is more than just name/value pairs, so I'm open to name suggestions.

      I agree with what you said about dictionary functions. As soon as I started writing these functions, I realized that it's a waste to mirror the dictionary functions. From the list above, I have only ended up using ErrorObject() and IsError(), so far.

      I think the ease of having one method of storing/testing for an error may trump the overhead involved in using a system like this. That may not be the case in a solution that rarely needs to log errors, though. All I can say for now is that I'm going to try this technique out for a while and will report back on it's effectiveness/ease of use.

    2. If the reluctance towards adoption of the Error functions I use (or any others) is preventing us from adopting something as a standard or best practice then I'm certainly fine with trimming things down. The verbosity of the functions is simply a convention to access the information at the time an error happens.

      Sure, anyone can acquire all the environment data within a script, but why invoke the duplication across multiple scripts. Just because you have a lot of information, and it's there, does not mean it needs to be a mental distraction if you don't see it. To me it's transparent and simply there when I need it.

      My use case is what drove my decisions and that was because I was emailing myself the environment data at the point in time when an error happens. In my particular case I'm dealing with a deployment of FileMaker which is detached and not centralized like a conventional FileMaker deployment.

      How about this. We decide on critical error information to include and then we use an Errors or ErrorLog table and maintain a list of suggested fields which simply auto-enter the environment data. That seems pretty clean and simple.

      Any given error record(s) could easily be scripted to be placed into an email or transferred anywhere else - which in my case is needed via REST. This would also allow us to assign that reference that Jeremy suggests.

      The method of implementation regarding the capture of environment information at error runtime is what we're debating here.

      1. I don't contest that a shortcut for creating a dictionary of helpful data at the point of an Error for logging and debugging purposes is a good practice. Long live the SSOFT principle! My reluctance to use the same data for programatic error handling within an application is more aesthetic than practical, and perhaps I should get over it.

        But if error log data are helpful for post hoc diagnosis and debugging, wouldn't a general-purpose event log be even better since we could see the chain of events leading up to the error? If we had that, would we expect some information to be logged that makes otherwise standard data to include in the error dictionary unnecessary (script name, for example)? If we had that, would it be better to have a whole class of state shortcut functions for logging different events, of which Error is just one of many? Others might include ScriptStart, ScriptEnd, SessionStart, etc.What format would an exportable emailable human-readable plain text file of the log take?

        1. One other reason I needed to capture environment data relevant to the error - when the error happens - is because of script branching. If you don't capture the information at that point in time then context changes and you may be in another script. I needed to know which script threw the exception.

          One downside to using a table is that some developers may not want to do this - even though it would be a good best practice any time you went into a solution developed against these standards. Logs are the halmark of the modern day OS and many good applications. You can always control the level of what the reporting is (e.g. normal, warn, critical, debug, etc.)

          I would hope we could meet these following requirements.

          • Limit the number of functions for the sake of simplicity and easier coding
          • Design something that works for both distributed and centralized deployments
          • Agree on standardized names for certain error (environment) values using the agreed upon Let format and code against them. Adding them to Reserved elements

          If only we had native sql inserts. (sad)

          1. Hello everyone (smile)

            As Matt points out logging is a good idea. But I would suggest that it is a separate concern. Error handling is a lower level concern, and should be able to work with or with out logging. 

            Even at this low level there is a need to capture some information other than code. Get(ScriptName) and Get(AccountPrivilegeSetName) for example change from the time of error capture to the time of handling the error.  So I am in favor of having a CustomFunction that captures that data to a String that can be handled along with the rest of # family.

            I hesitate to call it an Error Object, because it isn't an "Object" in the programming sense of the word. So why burden it with such baggage.

            Todd

      2. In my sample file, I used EAV model for the log table. I like this setup over a traditional table with fields for each piece of data because it can easily log any data you send to it. I've also set it up so the Let format dictionary isn't parsed into related records when the error is logged, to reduce the overhead of logging. If this method becomes the standard, we wouldn't necessarily need to agree on field names for the log table, but dictionary names that get logged.

        I agree that what we are discussing here is the method of capturing environment information, but it's difficult to properly discuss that without also discussing how that information is going to be used after it's captured (i.e. logging).

        1. But there is only a very small set of data that MUST be captured at the time the error occurs.  The rest is available later.  So why not just focus on capturing that?

    3. Regarding the name of these custom functions, how about using "data" instead of "object" (as Matt does)?

      1. "Data" is better.

        But I think we should trim this down to not deal with logging at all at this stage.  First things first.  Capture the error and other data that can't be derived later.  How about.

        ErrorGetLast

        returns the result of Get(LastError) and sets an $ERROR ( actual name to be determined ) to a LET string containing relevant data. 

        What I like about this is that you can shorten the number of scripts steps in the most common use case.

        If(not ErrorGetLast)
        ... handle the error
        ... you have $ERROR to work with. you can #Assign($ERROR)
        End if

        This mimics the built in Get(LastError), which I think is nice

        You could also have a function that "sets" the last error when necessary

        ErrorSetLast(error)

        These seems like a good base layer to me.  Logging can be laid on top.

        1. Welcome Todd! I'm so glad to have one of the communities best and brightest join in on our conversations!

          On the topic at hand. How about something that may be familiar to those coming from other languages into our small little world of FileMaker?

          ErrorCatch() ?

          I like staying inline with the familiar FileMaker'ese, but in this case it would have meaning to others familiar with the common try/catch and it is what we are doing. (smile)

          1. Thanks! (smile)

            The problem with adopting Try Catch is that it isn't really what we are doing. Try statements wrap around several lines/ statements of code and catch the error later.  We don't really have a nice way off doing that.

            What we can do is get the error value of the last step.  Thats it.  Implying otherwise is not so good, IMHO.

             

            1. Well... true, we're not capturing a true thrown exception based on a number of lines of code. However, any system generates an error based on a singular piece of code not working - just like a failed script step. Whether talking to the OS, a networking failure, you name it, some ONE thing caused the code to not work as intended.

              How about something even more obvious?

              ErrorCapture()

              1. We already have FileMaker code that refers to Error Capture,

                Set Error Capture On/off

                Would this get confusing?

                1. We can always take a look at things in the format of real code. I personally like the way this looks. I'm not SUPER concerned that EVERYONE will get it. The level of developer knowledge of those following these conversations is quite high and I'd like to feel we're creating something for our benefit first and then to the benefit of those who are learning.

                  As long as we're not creating gobbledy-gook (smile)

                  Set Error Capture [ On ]
                  If [ not #Assign ( ScriptParameters ) ]
                  Set Variable [ $$ERROR; Value:ErrorCapture() ]
                  Exit Script []
                  End If

                  Of course, this assumes the adoption of $$ERROR and ScriptParameters (wink)

              2. Both ErrorCatch and ErrorCapture imply to me that the function does more than just gather state data. Just "Error" rings true for object oriented developers used to naming conventions for constructor methods. I'd rather focus on making FileMaker developers feel comfortable than Java developers, but there are similar precedents in FileMaker's native calculation functions: Date, Time, Timestamp, and RGB.

            2. The single-pass loop error capture pattern discussed more recently by Soliant and in the past by Beezwax (I think?) is pretty similar to try/catch. Just think of the Exit Loop If [ErrorGetLast] as the "throw" statement. It's not a perfect analogy, but it's close. Nevertheless, we should hope to support more error handling patterns. I'm partial to guard clauses myself — keeps the error handling as close to the error itself as possible.

        2. I could get used to that. I've been using a code-only equivalent to this as a TextExpander snippet for about a year.

          I presume that ErrorGetLast would return the error from ErrorSetLast when Get ( LastError ) = 0, and vice versa. My guess is that ErrorGetLast would overwrite any results from ErrorSetLast when Get ( LastError ) ≠ 0?

          I want to throw out the idea — I'm not sure if it's a good idea or not — that a global $$ERROR variable is set. This would allow data on a runtime error to persist after the script that encountered it is long gone, such as for a parent script to get an error from a child script without unpacking the script result first, or for an error to be detected after a pre-event trigger whose result has other constraints.

          1. I presume that ErrorGetLast would return the error from ErrorSetLast when Get ( LastError ) = 0, and vice versa. My guess is that ErrorGetLast would overwrite any results from ErrorSetLast when Get ( LastError ) ≠ 0? 

            Not sure I follow...

            I think that to keep things as close to what is actually happening.  ErrorGetLast returns what ever Get(LastError) is at that time.  The only difference is that it also packages relevant data to LET string. ErrorSettLast(error) is simply a way to set the error.  I am not sure if that is what you were saying or not....\

            As for $$ERROR, I hear you.  But I have been bitten so many times by $$Error vars not getting cleared, when appropriate, it makes me nervous.

            1. I was thinking that ErrorGetLast would look at both Get ( LastError ) and $error (or $$ERROR), and act on whichever is most immediate — Get ( LastError ) taking precedence over $~error, then $($)error seems the only sensible way that might work, now that I think about it. Clearly that's not what you were thinking.

              The $$ERROR idea is most likely a holdover from me thinking about these as exclusively part of a logging solution rather than also being used for programmatic error handling. I was imagining accumulating events to a $$LOG that could be flushed to a table (or not!) at the developer's discretion. Some of that thinking must be bleeding through.

              Since I have functional leanings these days, even setting local $variables rather than just returning the dictionary makes me a little nervous. The gap between setting local and global variables is smaller to me than between returning a result and setting local variables.

              1. I have built systems like that before, and clearing of that $$Var is a real problem.

                To be clear...my idea is...

                GetLastError returns Get(LastError)

                AND

                sets a $ErrorVarToBeNamedLater to the dictionary error data.

                The very real problem that this doesn't answer is how to pass this Error State up the chain of scripts?  I think I would go with ExitScript[$ErrorVarToBeNamedLater], and let the calling script parse it with #Assign.

                Or maybe a a custom function for ErrorGetPerformScriptError.  I hate that name, but the idea would be that it would check both Get( LastError) and Get (ScriptResult) looking for errors. that might be nice.

                1. GetLastError returns Get(LastError)

                  I think GetLastError would have to evaluate $ErrorVarToBeNamedLater for a saved error before Get(LastError), otherwise calling ErrorSetLast(error) wouldn't do anything.

                  1. Yes it would. It would populate the ErrorData String to what ever you set it to.

                    1. I think I see what you mean now...

                      To test for a FileMaker error, you would use ErrorGetLast, but to test for an application/plug-in error, you would have to test the $ErrorVarToBeNamedLater - is that correct?

                      What I was looking for was a set of custom functions that would test for both FileMaker or application/plug-in errors.

                      1. yes that is correct.  but you would have to look at $ErrorVarToBeNamedLater for anything after the first GetLastError.

                        I think this maybe the confusing part.  Perhaps GetLastError should just return the error data?

          2. TOTALLY AGREE on the $$ERROR var.

            As well, we could also consider using our own (~) private conventions for generating application specific errors as opposed to just FileMaker's. Per my comments on the root page of Error Handling we need to account for a scope larger than just FM errors. Application, plugin-in and other errors need to be accounted for.

            Say for example the suite of # functions generated internal $~error values.

            This would mimic the way FM throws an error for the last step. We would throw our own $~error for the last custom function or script - what have you. This becomes an app specific error.

            If we outline the convention, then it sounds like we could make a good framework around it.

          3. You brought up something that I've been wanting to discuss: should these Error-related custom functions return information about an error that can then be evaluated to get the error status (as I set them up), or should they save data directly to variables (global or not) and return a boolean result?

            Benefit of returning error data:

            • I think the flow of code would be easier to follow for someone not familiar with the standard, since a value is returned from the main ErrorObject() function, which is then passed to IsError(), or sent to another script as a parameter/result; the user can see where the error data comes from/goes to.
            • no chance of having a global variable not cleared when it should be

            Benefit of saving error data directly to variables and returning boolean result:

            • fewer script steps required: a single function can set the data to variables, and return a boolean result that determines if an error was encountered or not
            1. I obviously favor option 2 ( since I proposed it ).  Way less work.  (smile)

              I don't think there is a problem with the flow of the code.  ErrorGetLast functions exactly as Get( LastError ).  It only adds the packaging of the data to a Let string.

              1. I obviously favor option 2 ( since I proposed it ).  Way less work.  (smile)

                I'm starting to lean that way too!

                ErrorGetLast functions exactly as Get( LastError ).  It only adds the packaging of the data to a Let string

                I don't see how this can handle Application or plug-in specific errors, though. It's also only valid as long as Get ( LastError ) is, meaning, I'd like to be able to save the value of Get ( LastError ) after a perform find step, then evaluate that error AFTER I restore the error capture state, which would modify the value returned by Get ( LastError ).

                1. Application and plugin errors could be handled by using ErrorSetLast(error, type)

            2. If we simply pushed the error on top of the stack within $$ERROR then the "getting bitten" issue might not be a problem. The ErrorWhatever() function could manage the $$ERROR variable and prevent it from growing too large - or actually better is using it's own internal setting to allow whoever implements the opportunity to set how much $$ERROR should retain.

              For example. I always increase the site of the history on the linux shell. Having control is nice.

              However in this case, pushing things into $$ERROR means that a developer MUST be familiar with how things are structure, otherwise they could just jack things up by mucking with it directly instead of leaving it to the domain of the Error functions.

              We would need to stipulate in the best practices that $$ERROR would be HANDS OFF. Except for reading. (wink)

            3. I'm with Todd. Despite my discomfort with setting variables via custom function when I don't strictly have to, convenience may justify it in this case, especially if we're going to allow developers to specify an arbitrary number of parameters. I would add that I prefer that the result not be strictly boolean — numeric error codes get cast as boolean when appropriate, which is fantastic. I like this:

              If [ErrorGetLast = 1]
              # Handle Error 1
              Else If [ErrorGetLast = 2]
              # Handle Error 2
              End If

              ... just as well as I like this:

              If [ErrorGetLast]
              # Handle any error
              End If
              1. I like it but I don't think it will work if you are basing it of off Get(LastError)

                 

                If [ErrorGetLast = 1]  --  Just Reset Get( LastError) to 0
                # Handle Error 1
                Else If [ErrorGetLast = 2]  this will always be 0 
                # Handle Error 2
                End If

                ... just as well as I like this:

                If [ErrorGetLast]
                # Handle any error
                End If

                 

                This might work work though

                 

                If [ErrorGetLast = 1] 
                # Handle Error 1
                Else If [#Get("error")=2]
                # Handle Error 2
                End If

                not perfect...

                 

                1. Unless ErrorGetLast falls back on $errorWhatever when Get ( LastError ) = 0.

                  1. Nice but you still have the same problem as with $$ERROR.  You could easily have false negatives

                    In your example above the error at the first Else If is really 0 as far as FileMaker is concerned. I think that is the key.  The error detection custom function is reflecting FileMaker's Error State. I think this is important.

                    1. The error detection custom function is reflecting FileMaker's Error State. I think this is important.

                      I disagree. If it was to do this, then it couldn't be used for app/plugin errors.

                      1. Why can't you use ErrorSetLast()?

                    2. According to the test I did just now, the If[] script step does not reset Get ( LastError ). If reflecting FileMaker's own error state is the only distinction between ErrorGetLast and ErrorSetLast, why not something like this:

                      If [Get ( LastError ) and ErrorSetLast ( Get ( LastError ) ; "fmp" )]

                      Due to logic short circuiting, ErrorSetLast will only be called when there is a non-zero error, subsequent Else If's checking for specific Get ( LastError ) values will still work, and we cut one function out of the mix without loss of functionality. ErrorSetLast would have to always return True (1) for this to work.

                      1. According to the test I did just now, the If[] script step does not reset Get ( LastError )

                        Wow thats awesome!

                        If [Get ( LastError ) and ErrorSetLast ( Get ( LastError ) ; "fmp" )]

                        If the point is to get rid of one custom function then I think I would prefer this

                        ErrorGetLast(overrideError, Type)

                        1. The idea is not just to get rid of one custom function, but to get rid of one function that is essentially a special case of another function, if I was understanding your descriptions of what they do correctly.

                          1. yes you understand them.

                            How about ErrorSave(error, type)

                            Error = Get(LastError), type = "fmp" by default.

                             

                             

                            Is that clearer?

                            1. Since we've got all this talk about the names used. I'm reflecting on what Jeremy promotes with regards to the singular nature and functionality of custom functions. For example, he broke out Fabrice's ObjectID function and I've since put that into one of my solutions (it's hard to break out of coding habits) (wink)

                              If we were to apply the same principle we would set up the base Error handing and assume it's just pure FileMaker.

                              We put that in the standards.

                              We then document a best practices and outline conventions such as ErrorSetApplication and ErrorSetPlugin

                              If we account for flexibility then we get more adoption. Not all solutions use plugins or generate/handle their own errors.

                              1. This makes sense to me - I like where you are going with it.

                                One thing I'd like to see is a single custom function that can test for any error from any source. We definitely need to be able to test for specific errors from specific sources, but often all we need to know is - did an error occur?  Then, from there we can drill down to the source of the error and the actual error code.

                      2. Now that I think about it, if that works, there's no reason this couldn't work just as well, and this would be more transparent about what it's doing (not to mention giving developers discretion over whether to use $error vs. $$ERROR vs. $$$heresHowItWentDownBoss). It also brings the conversation somewhat back to where we started where detecting an error and gathering state information are separate rather than integrated steps. I'm not sure if I like it or not.

                        If [Get ( LastError )]
                        Set Variable [$error; Value:ErrorGetLast ( Get ( LastError ) ; "fmp" )]
                        ...

                        I think part of what Daniel is getting at is that his goal was to create functionality that would effectively overload and be used in place of Get ( LastError ). The problem with that is that if we do it wrong, we could mix-up the source of the error. Would we want to build something like this using one GetError function:

                        If [/* FileMaker error */]
                        Else If [/* Plugin error */]
                        Else If [/* Scripting error */]
                        Else If [/* Custom function error */]

                        I think any solution will have to enable us to discern not only if there was an error, but which error. Accounting for multiple types of errors makes this harder than "Get ( LastError ) = 401". We might wind up with

                        IsError ( ErrorGetLast ( Get ( LastError ) ; "fmp" ) ; 401 ; "fmp" )

                        (this function would test for any error if the error code is left empty) or

                        ErrorGetLast ( Get ( LastError ) ; "fmp" ) = ErrorCodeFancyPants ( 401 ; "fmp" )

                        (the functions would return error codes specially formatted to prevent false matches between the same code for different error types). And we haven't even accounted for plugin and ODBC errors that don't even have error codes! I'm not sure a superset of consistent error detection functions is worth all this.

                         

                        1. IsError ( ErrorGetLast ( Get ( LastError ) ; "fmp" ) ; 401 ; "fmp" )

                          yuk (sad)

                          I'm not sure a superset of consistent error detection functions is worth all this.

                          I agree, I think we should handle FM's errors for the purpose of logging, etc and provide a pluggable system for extending it to allow for App and Plugin errors.

                        2. I think part of what Daniel is getting at is that his goal was to create functionality that would effectively overload and be used in place of Get ( LastError )

                          Yes, that was the idea

                          The problem with that is that if we do it wrong, we could mix-up the source of the error.

                          I hadn't given this aspect of it enough thought. You make a good point here.

                        3. Now that I think about it, if that works, there's no reason this couldn't work just as well, and this would be more transparent about what it's doing. It also brings the conversation somewhat back to where we started where detecting an error and gathering state information are separate rather than integrated steps. I'm not sure if I like it or not. 

                          I am beginning to see some problems as well.  Its hard to determine the source of the error.  Perhaps Daniel' s basic idea is better.  One function for saving the state as a LET string and one for detecting if the state contains an error.

                          <whew> this is hard work. (smile)

                          I am just at the point in a project where i really want to get this part nailed down.  I may just have to sally forth, with something close to this, and see how it feels

                          1. I am just at the point in a project where i really want to get this part nailed down.  I may just have to sally forth, with something close to this, and see how it feels

                            I'm in the same boat!

                            In a few hours, I hope to throw up a new set of custom functions based on Matt's latest comment.

                            1. I am having difficulty with this and might not post any new ideas today. I created a test file to help me visualize how the different methods would be used, here it is if anyone wants to test out their ideas...  Error Trapping.fp7

              2.  I would add that I prefer that the result not be strictly boolean — numeric error codes get cast as boolean when appropriate, which is fantastic

                Good point. I agree.

                The only issue I see with this (and why I suggested it return a boolean) is that some plug-in's return text error codes. I think many of these could probably be converted to a number with GetAsNumber(), though.  One's that cannot be converted to a number would just have to be dealt with appropriately when testing for an error.

  2. I feel almost like saying "Sorry".  I think I lead the conversation astray yesterday.  Perhaps I learned somethings. I feel like there is a critical principle or two that we bounced off of yesterday, but I can't quite articulate it.

    But here is where I have come back to after trying to use several of the ideas and concepts discussed yesterday.  Something much closer to Matts and Daniel's functions are much better than mine. The idea of having the main error capturing function automatically and magically populate an $error var, and returning the error number is a particularly bad one. There is way to much magic going on there. That probably explains why I even though I have tried some variation on that concept for years, but it never stuck.

    I now think the main Error State Capturing function should only return the Let String. And there should be one other IsError function that checks any string for the existence of an $errorCode value. This is clean, simple and easy to follow, all critical when it comes to error trapping.  It allows for the detection function to be used on any string, which would be useful for detecting Errors in Get (ScriptResult).

    In addition, it lets you easily capture and save more than one Error at a time, which is one of the first cases I hit yesterday, that told me something was wrong with my thinking.  

    Here is the current set that I am experimenting with.  This is very similar, ( I think ) to what Matt uses and closer to Daniel's original purpose.

    Error

    This is the main error state capturing function  Returns the Let String of containing the current Error state and other values that can't be derived later.

    IsError(error)

    Checks for $errorCode in the parameter and returns a boolean. If you pass nothing in then it checks Get(LastError).  This lets you use one function everywhere to check for an error. Even if you have no need to capture the state for the later.

    Custom types could be handled by additional Custom Functions that may or may not be used by a given solution. ErrorApp(code), ErrorPlugin(code), or ErrorCustom(type, code)

    I think I can handle all the common use cases with just these.  I can capture Error data for use later, I can use a single function to detect Errors, and I can pass errors back up the script stack, where they can easily be detected by IsError( Get(ScriptResult ).

    Thoughts?

    1. The idea of having the main error capturing function automatically and magically populate an $error var, and returning the error number is a particularly bad one. There is way to much magic going on there.

      This was my first opinion too. Using a method like this makes the flow of data difficult to follow (for anyone!), but like you said yesterday, it also requires more script steps.

      The level of developer knowledge of those following these conversations is quite high and I'd like to feel we're creating something for our benefit first and then to the benefit of those who are learning.
      - Matt Pattrowsky

      This comment really stuck home with me. In my version of the functions as seen in the 3rd Revision to this page, I was specifically making the functions more difficult to use so they would be more easily understood by a general audience. What I mean by more difficult to use is that I have to explicitly set the data to a variable, then evaluate that variable, rather than letting the custom functions do that internally.

      I still think this point is worth debating. I'd like to get some input from other members of the group.

      Todd,
      Have you used this file: Error Trapping v1.fp7 to see what your functions would look like in a script? That helped me get a real-world example of exactly how a script would look using my proposed functions.

      1. Daniel, I opened the file and played with renaming some of the functions.

        From a readability standpoint I think we're getting closer. I renamed the ErrorGet* to stay inline with the IsError.

        I ended up with stuff like

        If[IsErrorApplication]...

        and

        If[IsErrorFMP]...

        What I think might be more simplistic is what Todd is suggesting with just the two suggested simple functions as standards and we define which variables we're going to reserve for $errorVars.

        The one modification I would make is allowing IsError to support error type. Such as..

        IsError ( error ; type )

        Where type can now be defined by the developer. Something like..

        IsError ( Error ; Null ) or IsError ( Error ; "" ) //would default to fmp errors.

        But we could also have

        IsError ( Error ; "app" ) or IsError ( Error ; "whatever" )

        If Error has not yet been #Assign'ed, it can be overridden with things such as setting $errorType or anything else the developer want's to shove in and or override prior to heading into IsError().

        By being able to specify the type, we can expand beyond just FileMaker's errors but we're supporting it as the first class citizen. Boy, it would be nice if we had a Get ( FunctionNames ) and could then create something like FunctionExists() and then evaluate. (wink)

        1. Are you intending IsErrorFMP and IsErrorApplication to return a boolean result? How would you test for a specific error code?

          The one modification I would make is allowing IsError to support error type. Such as..

          IsError ( error ; type )

          I thought you made the opposite suggestion yesterday: Re: Error Information tracking

          If Error has not yet been #Assign'ed, it can be overridden with things such as setting $errorType or anything else the developer want's to shove in and or override prior to heading into IsError().

          I don't quite follow what you mean by this. Since you mentioned #Assign, I'm assuming you are referring to passing the error in the exit script step, to the calling script, then assigning it to a local variable(s)... but I'm not quite sure.

        2. IsError ( error ; type )

          I thinks this is a problem.  You don't always know what kind of error you might be encountering.

          Think about testing Get(ScriptResult)

          you end up with 

          (

          IsError ( error ; "fmp" ) or
          IsError ( error ; "app" ) or
          IsError ( error ; "plugin" )

          )

          yuk (sad)

           

      2. I agree with Matt that I want to facilitate the productivity of competent developers rather than developers who are learning to program. I don't agree that presuming that the next developer should know the same conventions I do is a best practice. The professional developer who has never heard of FileMakerStandards.org is a useful model of our future selves. We write code, the conventions we use change in unforeseeable ways, our memory of how we used to do things fades, and then we're called on to maintain that code. I spend about half my time in that mode, and I think it's worthwhile to target that situation for improving developer productivity. If we can look at unfamiliar code and understand the gist of what it does without running it or looking up the documentation, we're on a good path. If our reaction to developers who can't follow our code is, "you should read-up on the conventions," that's a bit selfish.

        Assigning variables within a custom function that a developer is supposed to know by convention to interact with is not robust to the needs of the competent-but-unfamiliar developer. The #Assign function walks a fine line on this point that I've learned to tolerate. The Triggers* functions and some of the UUID functions I've written also set variables, but they don't expect developers to ever interact with those variables directly — only through other functions in the same family.

        I'd rather have more script steps and greater transparency. For example, it's possible to define complex find criteria in a single Perform Find [] script step, but it's much easier to follow a script that does an Enter Find Mode, Set Fields, Perform Find sequence because you don't have to porpoise through dialogs to see what the find requests are.

        I find the competing needs of developers familiar and unfamiliar with our conventions less challenging than finding a good balance between the needs of transparency about how code does what it does and abstraction freeing me to think about progressively larger scopes of functionality and eventual get around to deploying useful applications.

    2. This is more of a side-note than anything, but recently I came across an error importing custom functions into a new database file. One function's name matched a parameter name of another function and it caused the import to fail. So, given your suggested Custom Functions, I would recommend using IsError(theError) to prevent this from happening (this is why I've been pre-pending all custom function parameter names with "the").

      If IsError("") is exactly equivalent to Get ( LastError ), then I would suggest not using IsError("") for clarity's sake.

      How would you test for a particular error code from a particular source? (like 401 from fmp, or 2 from app, etc.)

      1. IsError("") is exactly equivalent to Get ( LastError ), then I would suggest not using IsError("") for clarity's sake.

        Good point

        How would you test for a particular error code from a particular source? (like 401 from fmp, or 2 from app, etc.)

        I could just use #Get($error, "type") = "fmp" &  #Get($error, "error") = 401

        Although I think custom function to make this easier wouldn't be too bad.

         

  3. One aspect of Application errors is that they will rarely need to be tested for. What I mean by this is that an Application error is a developer-defined error that is generated when the developer has tested for some condition (valid parameters, let's say), then if that test fails the developer will likely exit the script with an error code that they designate.  (this is what I do, at least)

    On the other hand, FileMaker errors must be tested for with Get ( LastError ) to determine if an error occurred.

    I believe plug-in's could go either way.  Some plug-in's offer the equivalent of Get ( LastError ), others don't.

    These are just my observations of the fundamental differences between the different error types that I noticed when I was creating test scripts using these error tracking custom functions. I also found that having separate functions for each type of error was convenient because I could include/omit the parameter depending if it needed to test for an error, or have an error manually specified.

  4. What about when you pass errors back up the Script Chain through Exit Script?  Wouldn't you need to detect it then?

     

    1. Using the functions as I've defined them in the current version of this page (v4)...

      Exit Script[#( "error" ; $error)]

      Then, in the parent script, to test if a sub-script returned an error result:

      #Assign ( Get ( ScriptResult ) ) and IsError
  5. Are you nesting the error hash inside another one?

    1. Yes. The variable $error contains name/value pairs for errorType, errorCode, errorDescription, and any other data the developer wants to capture when the error occurs.

      Here is a link to the custom functions I'm working on: https://github.com/dansmith65/FileMaker-Error-Handling/tree/master/Custom%20Functions
      I'm still working on the sample file to go with this.

      1. I tried that as well.  I had an Error and Result packaged as one string.  In fact my IsError function can detect an error that is one level deep. But do we really gain anything by nesting. Get(ScriptResult) is likely going to contain either an error or a valid result. What do we gain from the added complexity?

        1. I think packaging the error data as a single name/value pair is "cleaner" since the $error variable contains more data than just an error code.

          I also think it's easier to work with (assuming you are using the functions as I've defined them). What I mean by easier to work with is that you can easily assign the error from the script result to a local variable named $error, which then allows you to use these custom functions: IsError, ErrorGetFmp, ErrorGet*

          I mentioned this before, but just for reference, you would assign the error data from the sub-script to a local variable named $error like this:

          #Assign ( Get ( ScriptResult ) )
        2. Speaking of complexity. One thing it sounds like we might need to address is when an error is handled. If I was opening a solution I had not worked on, which used these standards, I would hope they handled (dealt with) the error within the same script where it was generated.

          Passing any error back through Get ( ScriptResult ) is something that enters the logging/notification phase of things.

          Although I know within the context of a loop, there are times where you just want to log an error (such as on one given record that may be locked) and you want all others to be processed normally.

          Man, why is this so complex?

          1. I would separate notification and logging.  I need to notify script up the chain that an error occurred. But I don't have to log it.  Notification is just making it possible for the calling script to "detect" an error in the subscript. Isn't it?

            I would handle the error where it occurred, but I do need some way to let calling scripts know that an error occurred.  Don't I?

            I don't use Halt much, so perhaps I missing something there.

            1. You have both mentioned "handling" an error, I'd like to clarify what you mean by that. What I assume you mean is that any necessary clean-up is performed and/or logging the error (if necessary). Do either of you mean anything other than that?

              I would separate notification and logging.  I need to notify script up the chain that an error occurred. But I don't have to log it.  Notification is just making it possible for the calling script to "detect" an error in the subscript. Isn't it?

              I agree. Another item that is related, but should be kept separate is notifying the user that an error occurred.

              I would handle the error where it occurred, but I do need some way to let calling scripts know that an error occurred.  Don't I?

              Yes, I think so - that's how I write scripts.

              I don't use Halt much, so perhaps I missing something there.

              I don't think I ever use halt anymore. I always notify the calling script that an error occurred, then as long as the calling script tests for errors returned by the sub-script, the halt script step is not needed.
              1. By handling I mean
                1. clean up
                2. optionally notify user
                3. optionally log
          2. Resolving an error in the same script that generated it is a good preference, but I think it could be damaging to be too rigid about it. Another practice I might throw in to that kind of preference is that a subscript should either resolve an error and carry on as if nothing happened, or stop what it's doing and notify the parent script, but almost never both. This minimizes confusion on the part of the parent script about whether or not it's the more appropriate scope for the resolving the problem. (This would also increase the importance of Todd's recommendation that logging always happen in the same scope where the error was detected, since no other scope may get to see it if it's resolved immediately after detected.)

            1. hmmm.  Not sure that I made that recommendation. (smile)

              I almost always use a subscript to log, which by definition means a different scope.

              1. But a subscript of the scope where the error was detected, rather than a parent; right?

  6. I'd love to get some feedback on the "FileMaker-Error-Handling.fmp12" file in my GitHub repo. If you are going to review it, the "test error trapping pattern script" is the parent/calling script that calls any of the scripts in the "Error Trapping Patterns" folder. This script needs to be modified to test the different error results returned by the different scripts.

    The "Error Trapping Patterns" folder contains 4 scripts that perform the same task, but use different error trapping patterns in each script (these scripts originally came from here: Error Trapping and Resolution Patterns). These scripts are meant to give you an idea of how the custom functions on this page would be put to use in the real world.

    The "Error Logging" folder contains scripts that manage logging errors from other scripts. This is outside the scope of the conversation we are having on this page, though. I plan to eventually make a new page to discuss logging.

    1. I created a new branch of my GitHub repo named return_data in which I modified the custom functions and the sample file to return the Let format dictionary (set of name/value pairs), rather than setting them directly to $error variable.

      It does take a few extra script steps to use this method, but not too many more. Perhaps the clarity is worth it?

      1. I like it better.  It is very clear.

        My preference would be to save the error before the IF and then test using isError($error) but either pattern is supported by this.

        By the way I prefer the before the IF option because of getting burned by $error being EMPTY sometimes, which it might be in this case.  But the use of IsError($error) would catch that.

        1. By the way I prefer the before the IF option because of getting burned by $error being EMPTY sometimes, which it might be in this case.  But the use of IsError($error) would catch that.

          I don't quite follow what you mean by this. Which script/section are you referring to?

          1. in the script called "Guard Clauses ( id ; { layoutName } )"  you have this.

            Set Field By Name [GetFieldName ( GetField ( "id" ) ); $id]
            If [Get ( LastError )]
              Set Variable [$error; Value:ErrorSaveFmp]
              Go to Layout [original layout]
              Enter Browse Mode []
              Perform Script ["Create Log Entry ( logData )"; Parameter: # ( "logData" ; LogData ( "error" ) & $error )]
              Exit Script [# ( "error" ; $error )]
            End If

            you are are setting the error After the IF statement.

      2. Using the "return data" method replaces many instances of this:

        Set Variable [$trash; Value:ErrorSave*]

        with this:

        Set Variable [$error; Value:ErrorSave*]

        which I like MUCH better

        1. Yeah ErrorSave is better. but its not really saving its "getting".  

          Set Variable [$error; Value:ErrorGet]

          or your idea from before

          Set Variable [$error; Value:ErrorData]
          1. I think you're right - the "Save" part of the name came from when I was saving data directly to a variable - but now I like ErrorData* better too.

            I updated all the function names - what do you think of them now?

            1. I like these better

  7. I took a look at the file.  And I would have liked it much more yesterday.  (smile) I am now really having a hard time with the hidden population of the $error var.

    Also Is this how you have to test for an error in a subscript?

    #Assign (#Filter ( Get ( ScriptResult ); "error" ) ) and IsError

    That seems a bit wordy

    1. Using the #Filter() function is optional. You can read about the #Filter function here: https://github.com/jbante/FM-Parameters#filter--parameters--filterparameters- or a related discussion on this site here: Re: Custom Functions » Script Parameter Interface

  8. Regarding the to do item: "define the recommended data that should be saved when an error occurs"

    I think we have already agreed that these function should not return all data that would be logged (if the error happens to be logged), but only the data that is likely to change from the time the error occurs to the time it is logged - with an emphasis on reducing the overhead created by using these functions repeatedly throughout a script. I also think we have agreed that the script that generates the error should log the error.

    By that criteria alone, the errorDescription, ScriptName, and ScriptParameter should not be returned by these functions (but they are now, as the functions are currently defined).

    In spite of the above...

    I like the idea of returning the errorDescription because it could help with debugging a script. When walking through a script with ScriptDebugger and an error occurs, you can immediately see the description of that error in the DataViewer.

    I also like the idea of returning the ScriptName/Parameter because then the error that get's passed up to the parent script will contain this information. If the parent script logs the error (like I do in "test error trapping pattern script" script), that log entry will contain Get ( ScriptResult ), which contains the error that occurred in the child script, which contains the Script Name, which allows that log entry to contain both the parent script an child script name. (sorry if this explanation is confusing)

    1. ScriptName, and ScriptParameter will change if you use a subscript to handle logging or user notification.  I think you have to include them.

      1. I think this is the point where we need to create a new page and discuss logging. The logging method I implemented uses the same concept as this error tracking method; use a custom function to capture the information. You can see this at play in my FileMaker-Error-Handling.fmp12 file at GitHub. By using this method, it could capture the ScriptName and ScriptParameter before calling a logging script.

        I don't have the time to start a conversation on logging right now though. I think I'm going to get back to work and will implement these error tracking functions and my logging method as they are currently defined, then I will work out the details as I go.

        1. I agree that how you log or notify is a separate topic. Perhaps we should focus only on those values that will help the debugging, detection and programatic handling of errors.  I think ScriptName could fall in there.  But maybe not ScriptParameters.

           

    2. The ErrorData* custom functions should capture data that is only available immediately after the error occurs (because it may be changed by error-handling script steps) and data that will help with debugging, detection, and programmatic handling of errors.

      Do you think this statement covers the intention of the ErrorData* functions?

      Here is the data that I think meets the above criteria:

      • errorType
      • errorCode
      • errorDescription
      • Get ( CurrentHostTimestamp )
        • would not change much between the time the error occurs and it is logged, unless the logging is done after a long looping operation. It could help correlate script errors with server log entries.
      • Get ( ErrorCaptureState )
        • this is likely to change after the error (I set error capture on before a find and off after the find)
      • Get ( LastODBCError )
        • is this only available immediately after the error, like Get ( LastError )? should be disabled on systems that don't utilize ODBC integration
      • Get ( ScriptName )
        • useful for debugging
      • Get ( ScriptResult )
        • a sub-script may be used in error-handling steps, which would modify this value, so it should be captured when the error occurs
      • Get ( UserCount )
        • the error could be related to CWP, and CWP connections are not persistent, like FileMaker Pro client connections are, so this value should be captured when the error occurs.
  9. Has anybody been using these custom functions? Or a modified version of them? If so, do you have any additional feedback?

    I've been using, and like them. In my working copy, I have changed the ErrorDataFmp function to take a parameter, rather than always testing the current value of Get ( LastError ). So, now the function is ErrorDataFmp ( theErrorCode ). I did this for two reasons:

    1. Previously, there was no way to get error data for an error code returned by EvaluationError ( expression )
    2. Consistency across this suite of functions/clarity of code. I think it's easier to understand what the function does when you explicitly pass it Get ( LastError ).
  10. I have been using them.  I agree that there is a need to be able to set the error.  Also I find it useful to be able to set a Custom Messsage or Note, that gets displayed or logged.  This helps to give some context or additional info for the error.  For example you might want to tell the user what parameters where missing, etc.

    I have been using #Set to set add values to the error.

  11. If by #Set, you mean # ( name ; value ) found here: Custom Functions » Name-Value Pairs, then I have been doing this too. What have you been using for the 'name' of these additional values? I've used: "additionalInfo" and "errorInfo". I think it would be nice to standardize the naming used for the note that goes with the error. I also add variable values to the ErrorData, so I end up with additional miscellaneous name/value pairs like "selectedCustomer", "dateStart", "dateEnd".

    I haven't set custom message text to the $error variable yet, but that is something I was considering too. What name did you use for that?

    Have you also been using the LogData ( logLevel ) custom function?

    How are you logging? Did you use an EAV-style log like the one that can be found in FileMaker-Error-Handling.fmp12?  This is what I have been using, and I find that my "additionalInfo", "errorInfo" or variable value entries are what I often want to see, but they are kind-of 'lost' in all the other data. This is the biggest issue I have with the LogData function and the logging method I am using.

  12. I just updated my GitHub repository to reflect the current set of custom functions I'm using for error information tracking: https://github.com/dansmith65/FileMaker-Error-Handling/commit/a1ad8d854576096a05ffdda2f9b79bed653be3e4

    I've been using one variation or another of these custom functions for almost a year now and think they are almost ready to be added to the filemakerstandards.org repo. Before I submit a pull request, I would like to invoke one last discussion to see if there is any more fine-tuning to be done.

    I'm not going to explain in detail what every function is supposed to do; I'll let the code speak for itself. There are sample scripts in the "Error Trapping Patterns" script folder of the FileMaker-Error-Handling.fmp12 file in the GitHub repo. These scripts give an example of how these functions can be used

    1. Overall  am happy enough with the functions as you laid them out.  One question though...

      #GetLastError and #FMPError both contain the map of FileMaker error codes to error text.  Was that intentional?

      Todd

  13. I'm going to attempt to address all recent issues/questions brought up either in this thread, or one of these:

    I chose the name #GetLastError over Jeremy's suggestion of #LastError to maintain the association with the built-in function Get ( LastError ). The biggest reason for wanting to maintain this association is to make it as clear as possible that this function internally uses Get ( LastError ).

    Generally speaking, I like namespacing custom functions, so they are grouped together when sorted alphabetically. However; I wasn't able to come up with a naming scheme that would fulfill that criteria and still read well in a calculation. Since it seemed like I could have one or the other, but not both, I choose readability. I would welcome name suggestions, though.

    The reason #GetLastError and #FMPError both map FileMaker error codes to error text is so one function can be used without the other. Someone who never want's to 'throw' a FileMaker error will never need #FMPError and could get by with just #GetLastError. On the other hand, someone who likes to throw FMP errors all the time (or save the error to a variable and encode it later) may only want to use #FMPError.

    #AppError is meant as a template only (it is populated with error codes I use). It is expected that this function be modified with your own error codes. A new custom function could be created for every error source, if the developer desired. As an example, you may want an error handling custom function for every plug-in you use.

    1. Looking at the functions today while integrating into a new solution. Here are the things I've noticed.

      • Custom functions aren't fully following Let variable naming because they lack the (~) tilde for some locally scoped vars in places.
      • Personally not as fond of theErrorType as opposed to the shorter errorType, etc. I think "the" is implied. Plus, #Error isn't called within solution code as it seems like it's pretty much a private function for #FmpError, #AppError and #GetLastError.
      • Re: grouping them in the # (hash) functions for parameters. I'd like to see the name spacing separate them out. I'm attaching screen shots of what I've played with. As you'll see, I've played with ! and : to push them up. The ! is probably too much of a "not" from other languages. The colon may work. I like that being within the # functions means you need some of them too - but only looks like #Get is needed. So grouping within # may be diluting #'s distinct grouping.
      • I agree with Todd that having the error descriptions in more than one place is not DRY. I would opt for a singular location. If this meant another private function, then I would go with that. Yeah, it's not too big of a deal when the next version of FMP adds a few new errors, but why have to update them in multiple places?
      • Since you and Jeremy have started the nice trend of making implementation files for suites of functions it would be nice if we can find names for tables and scripts. I name my tables something like Errors or Errorlog. I would love to be able to just open the file, copy/paste the CFs, Errorlog table and necessary scripts.

      So far, this is what I have. I'll give more feedback as I start to code around them. However, don't let this be a stopper for pushing these up in. Let's push them in and make the changes as we go. That's the advantage to how we're doing things.

      This is GREAT work DAN!!!

  14. Custom functions aren't fully following Let variable naming because they lack the (~) tilde for some locally scoped vars in places.

    Thanks, I'll have to look into that; I hadn't noticed.

    Personally not as fond of theErrorType as opposed to the shorter errorType, etc. I think "the" is implied.

    There is a practical reason for my choice of naming here. It's to prevent name collision with other custom functions or new functions added by FileMaker. Let's say you have two custom functions: FieldName ( field ) and TableName ( fieldName ). If the FieldName CF already exists in the file and you try to add the TableName CF, you will get an error because the parameter fieldName matches a previously defined custom function name. To prevent this, I've started adding "the" to many parameter names on custom functions I write, since it's still readable but will likely prevent naming collisions.

    Plus, #Error isn't called within solution code as it seems like it's pretty much a private function for #FmpError, #AppError and #GetLastError.

    As used by the sample file, #Error could be considered a private custom function. However, as designed, it is not. I would like this custom function to be available for use by elements other than these custom functions. As an example, I wrote a module in modularfilemaker.org format (currently unreleased) which directly uses the #Error function to create an error that is scoped to that module. You could also use this function directly when testing for an error after use of a plug-in (in the event you decided not to create a custom error handling CF for said plugin).

    grouping them in the # (hash) functions for parameters. I'd like to see the name spacing separate them out.

    I kind of like the idea of namespacing these functions, but have yet to see or come up with a naming scheme I like. As you said, the exclamation mark reminds me too much of logical NOT from other languages. The only reason I can articulate for not liking the "#:" prefix is that it seems like too many non-descriptive characters. More than anything, it's probably just too new and unfamiliar, therefore I am repelling the idea. I could probably be convinced to use this prefix if others agree to it.

    I agree with Todd that having the error descriptions in more than one place is not DRY. I would opt for a singular location. If this meant another private function, then I would go with that. Yeah, it's not too big of a deal when the next version of FMP adds a few new errors, but why have to update them in multiple places?

    In the past, I think I would have agreed, but Jeremy Bante convinced me otherwise in this comment.

    Previously I was always thinking a sub-function would need to be a private function namespaced the same as the other error custom functions, but it just occured to me that it could be a generic function like ErrorDescription ( theErrorCode ). Is that what you had in mind?

     Since you and Jeremy have started the nice trend of making implementation files for suites of functions it would be nice if we can find names for tables and scripts. I name my tables something like Errors or Errorlog. I would love to be able to just open the file, copy/paste the CFs, Errorlog table and necessary scripts.

    I think I started with names like that, then realized that logging isn't limited to errors, so I changed the table name to ScriptLog. I've found my logging scripts handy for logging miscellaneous information with the "info" log level. This is especially useful when a script is running on the server.

    I plan to put these custom functions in a modularfilemaker.org formatted module at some point. I could include a logging routine with it, which would work like you wanted: copy the tables/scripts/functions into a new file to add these features. I've been using (a slightly more refined version of) the logging method as seen in FileMaker-Error-Handling.fmp12 and my biggest issue with it is slow performance in list view over WAN. This is due to the use of filtered portals to view related data. If anyone has a viable alternative to this, please let me know.

    1. I agree with Todd that having the error descriptions in more than one place is not DRY. I would opt for a singular location. If this meant another private function, then I would go with that. Yeah, it's not too big of a deal when the next version of FMP adds a few new errors, but why have to update them in multiple places?

      In the past, I think I would have agreed, but Jeremy Bante convinced me otherwise in this comment.

      The example I was using in that comment referred to the particular case of UUID functions, of which only one is used for most applications — copying only one UUID function from the available selection in one file into another makes perfect sense. These error custom functions are a different situation, I think. They work in concert with each other — they make more sense when you import the whole set into an application. If I'm already importing Error, ErrorFmp, ErrorGetLast, and ErrorFound into a file, it's not substantially more of a burden to also import ErrorFmpDescription ( errorCode ).

      1. I don't know why I didn't think of this before, but if nobody want's to use only ErrorFmpGetLast, then ErrorFmpGetLast should just reference ErrorFmp.

        I actually like this change for another reason: it's now very clear what this function does. I think people were confused by it, but It's really just a shortcut. The reason I wrote it them as separate stand-alone functions is because you can get by with ErrorFmpGetLast at least 99% of the time, and some people could get away with it 100% of the time. The only scenario I can think of where you have to use ErrorFmpGetLast is when you use the EvaluationError function.

        Does this change satisfy everyone's need to keep it DRY?

  15. Ok, I can see the validity in some of your reasoning with the "the" but I'm personally not one to worry about what FileMaker might do in future versions. If they change/add some collision then we just adapt - simple.

    Since we direct the standards, we can declare errorWhatever as reserved. I would opt for concise clarity over increased verbosity.

    I would personally REALLY like to keep the error functions out of # because of the dilution effect. If I was to open the Custom functions dialog for anyone and they saw all the # functions with the error grouped in then it becomes harder to grok. YES, I get that the error functions rely on # and #Get, but I would hope that anyone who is following filemakerstandards.org is not breaking out fundamental chunks from each other. If I'm using # I'm using error too. I would hope others would too. That's part of the benefit.

    For log naming, I doubt we can get away with syslog (wink), but I agree that making it non error specific is a good way to go. Simply "Logging"?

    So here is what I've been implementing for those interested. I personally prefer a very simple system for handling errors. Here is a sample of how I'm calling things.

    Script with error
    GotoLayout[ ]
    Perform Script [ "Handle Error ( error ; dialog ; capture ; halt )";
        Parameter: # ( "error" ; Error:GetLast ( "Go to some layout" ) & LogData ( "warn" ) )
        & # ( "dialog" ; True )
        & # ( "capture" ; True )
        & # ( "halt" ; False ) ]

    As you can see, I use a single script named Handle Error (). This takes the options of the error data, (bool) dialog, (bool) capture, (bool) halt.

    This seems to facilitate the bulk of most of my error handling needs as I can optionally present the error, capture the error and bail out of the script.

    Here is the Handle Error script code I'm using.

    Handle Error Script
    #----------------------------------
    # Capture parameters
    #----------------------------------
    If [ not #Assign ( #Filter ( Get ( ScriptParameter ) ; ScriptRequiredParameterList ( "" ) ))]
        If [ Developer ]
            Show Custom Dialog [ Title: "Developer Error!"; Message: Quote ( Get ( ScriptName ) ) & " could not process the required variables."; Default Button: "Darn", Commit: "Yes" ]
        End If
        Exit Script [ ]
    End If
    #----------------------------------
    # Default handling
    #----------------------------------
    If [ Error:Caught ( $error ) ]
        #
        # Show dialog
        If [ $dialog or IsEmpty ( $dialog ) // default is to show dialog ]
            Show Custom Dialog [ Title: "Script error"; Message: "Script " & Quote ( #Get ( $error ; "ScriptName" ) ) & " generated an error.¶"
            & #Get ( $error ; "errorDescription" ); Default Button: "OK", Commit: "No" ]
            # Calling a subscript which shows a dialog can be set here. You can use another FileMaker window, a plugin dialog or a standard FileMaker step of Show Custom Dialog. By calling another script for the dialog you increase your programmatic flexiblity.
            // Perform Script [ "<unknown>" ]
        End If
        #
        # Capture error
        If [ $capture or IsEmpty ( $capture ) // default is to capture ]
            Perform Script [ "Capture Error ( error )"; Parameter: # ( "error" ; $error ) ]
        End If
        #
        # Halt all execution
        If [ $halt // default is to continue on error ]
            Halt Script
        End If
    End If
    #
    # RESULT
    Exit Script [ ]

    Note that I'm currently trying the following naming

    • Error:any <- #Error
    • Error:fmp <- #FmpError
    • Error:app <- #AppError
    • Error:Caught <- #IsError
    • Error:GetLast <-#GetLastError

    Read the above and see how it sits with those following this thread.

    Matt

  16. Regarding "the" prefix to custom function parameters. I still think it's a good idea for these functions because the parameter names are fairly common. By that I mean they are generic enough to already be in use as custom function names by others... Actually a search at http://www.briandunning.com/filemaker-custom-functions shows that ErrorType and ErrorDescription do exist. In addition to that, I was already considering creating a custom function named ErrorDescription to move the common code from #FmpError and #GetLastError into a single custom function.

    I think I agree with keeping the error functions out of the "#" namespace. I also like the newest set of names you came up with. Here's a slight variation on your idea that I'd like to put forth for comments:

    • ErrorAny or Error <- #Error
    • ErrorFmp <- #FmpError
    • ErrorApp <- #AppError
    • ErrorAsBoolean <- #IsError
    • ErrorGetLast <-#GetLastError
    1. "By that I mean they are generic enough to already be in use as custom function names by others..."

      Why exactly would we be worried about preexisting custom functions on other sites? I don't want to come off as elitist, but if I was finding these standards for the first time, and I had used something else prior, I would simply rename the older stuff, adopt the newer cleaner more efficient stuff and slowly weed out the older functions.

      At least that is what I did with the Theme Studio when Jeremy enhanced # from what had started almost a decade ago with Mikhail Edoshin. FileMaker's ability to rename at whim is a key advantage. I don't think we should use other functions as a determiner of what directions we take. These are conventions that we get to determine for our own use and those who choose to follow them. Am I "wrong" in my thinking?

      I'm not as fond of using a data structure as part of a name (e.g. ErrorAsBoolean).

      Revisiting my 2010 file on Error handling from my magazine site shows I had used Error, ErrorData and ErrorString. I like the break out of Error to be more simplified with the ability to specify FMP vs. APP errors. I agree with using just your basic "Error" for the core error function.

      I just shot a video today about using the Error handling stuff and I've given you props for all these great enhancements. My preference would be the following.

      • Error <- #Error
      • ErrorFmp <- #FmpError
      • ErrorApp <- #AppError
      • ErrorCaught <- #IsError
      • ErrorGetLast <-#GetLastError

      I'm pushing for something more verb like because it reads well within If's.

      • If ErrorCaught
      • If ErrorThrown
      • If ErrorHappened
      • If ErrorOccurred (don't like much because it's easy to misssspell - yes, I did that on purpose!)

      vs.

      • If IsError (takes it out of Error prefix grouping)
      • If ErrorAsBoolean

      So far, I think we are EXTREMELY close in finalizing this. Let's get some chime in on the mailing list about the naming of things and move this into best practices.

      1. Regarding "the" prefix to parameter names.

        No, I wouldn't say you're "wrong" in thinking that someone adopting these function would ideally rename/remove old error handling CF's before using these.

        I would make a few points in favor of "the" prefix, though:

        1. The ideal method is not always used
        2. It provides a feature: "less prone to errors when installing these or other CF's", at a very low cost.
        3. Personally, I use a text expander to "type" all these custom functions whenever I use them, so I never even see the parameter names. As long as the parameter names are descriptive enough, they are almost irrelevant for me.
          example: this is the text my expander auto-types: ErrorApp (  ; "" ) 
  17. In general I like the direction.  I might suggest "ErrorFound" instead of "ErrorCaught".  Thats about it though.

     

    1. I can live with ErrorFound.

    2. I like it too. I'm going to miss "IsError", but I prefer the namespacing benefit of "ErrorFound".

  18. Oh and for the sake of moving into Best Practices I think we need to define one open custom function for where app/plugin errors can be defined and managed. This could be something like any of the following.

    • ErrorCodes (may be confusing with FMP errors as opposed to solution errors)
    • ErrorStrings
    • ErrorSolution
    • ErrorApplication

    Just something where a reserved custom function can be used in conjunction with LogData calls. Such that calling

    ErrorApp ( 9999999 ; "Run some required script" ) & LogData ( "critical" )

    is functionally equivalent to 

    ErrorApp ( 9999999 ; "Run some required script" ) & LogData ( "critical" ) & ErrorApplication ()

    I know you guys seem to have bigger complaints about dependancies than I do (or so it seems so) but integrating something like ErrorApplication into LogData seems to make things easier.

    If I'm going to integrate both fmp and app/plugin error handling into my solution then I need a place to define/manage my app/plugin errors. Putting a call to this within LogData and leaving the function reserved, but blank, seems to make sense to me.

    With all Error related CFs grouped by prefix (outside of LogData) I can copy/paste in one go - then define my solution errors as I bake them in.

  19. I don't follow your train of thought here; either you missed the intention of the custom function as I set them up, or I missed the intention of your suggestion.

     I need a place to define/manage my app/plugin errors

    ErrorApp ( theErrorCode ; theErrorInfo ) is the place to do that for Application errors. Currently, it contains my personal error codes/descriptions, which are meant to be used as an example; anyone using this function should modify it as necessary for your application.

    Error[source] ( theErrorCode ; theErrorInfo ) is where plugin errors would be defined, where [source] is the name of the plugin.

    Does this clarification provide you with the feature you need? If not, please provide more information about your suggestion.

    1. Yeah, it does clarify it. However, my thought was a more centralized location. Rather than having to create ErrorScriptMaster, ErrorTroiDialog, etc. I was thinking that one function could be called by ErrorApp.

  20. So far, given that ErrorStrings isn't really within FileMaker anywhere, it seems to fit well. I tried ErrorApplication, but that's too close to ErrorApp. Here's a screenshot.

    1. I'm still not sure what the purpose of the ErrorStrings function is.

      1. Oh, you can ignore the implementation I was describing. I don't think it would go well within LogData.

        Essentially, what is needed is a centralized location to store and document application and plugin error strings. The ErrorFmp and ErrorGetLast return the human readable string for any given error code.

        When you start to create your own error codes (such as say -1 for "Couldn't parse script variables") you need a place for these to be translated so they can be injected into the log.

        I didn't fully think out my implementation, but there needs to be something like this as a placeholder.

        Does that make more sense?

  21. I just added a few customized error handling functions to my GitHub repo, meant to demonstrate how this suite of custom functions can be extended.

    ErrorSomeWebService will extract the faultcode and faultstring from a soap fault. This can be customized to work with any XML based web service and "SomeWebService" should be changed to the name of the web service.

    ErrorScriptMaster can be used with the ScriptMaster plugin. Again, it can be customized to return specific error codes that you want to test for.

    As is, both of these custom functions use -1 as the error code since both of the error sources use text based errors instead of numeric errors (I suppose that may depend on the web service, though).

  22. Matt, I'm going to respond to both of your latest comments in a new top-level comment, rather than trying to address each separately.

    Essentially, what is needed is a centralized location to store and document application and plugin error strings

    I disagree that this is needed. Can you explain why you think it's needed? One of the basic principles I designed these function on is that there would be a separate function for each error source.

    When you start to create your own error codes (such as say -1 for "Couldn't parse script variables") you need a place for these to be translated so they can be injected into the log.

    Every Error* function (other than IsError/ErrorFound) returns a code, description, and type/source for the error, so the place that a numeric code is translated into a description is the customized error functions (ErrorApp, ErrorFmp, ErrorScriptMaster, ErrorGoogleAPI, etc.). In other words: if the output of these functions is logged, the log will contain all relevant information about the error. Of course, you can use the LogData function to gather more environmental data, but that data is not directly related to the error.

    Yeah, it does clarify it. However, my thought was a more centralized location. Rather than having to create ErrorScriptMaster, ErrorTroiDialog, etc. I was thinking that one function could be called by ErrorApp.

    I disagree that a centralized location is required. However; if you choose to, you could save all your errors in the ErrorApp function, in which case you would only have errors of type "app" or "fmp" and never "ScriptMaster" or "GoogleAPI".

    Personally, I think it makes perfect sense that each error source have a unique error handling function because errors from each source must be handled differently. Take my demo ErrorScriptMaster and ErrorSomeWebService functions as an example. The parameter to ErrorScriptMaster is the result returned by the plug-in, because that value will equal "ERROR" if an error occured. On the other hand, the parameter to ErrorSomeWebService is the xml returned by the web service, from which the error is extracted (and transformed, if necessary). If you try to create one function to take the place of these two you either have an overloaded function, or you have to manually convert/extract the error.

    1. Consider this script that tests for an error from two different plug-ins and Filemaker:

      # use ScriptMaster plugin
      Set Variable [ $result ; Value:SMSetVariable( "test" ; "this is just a sample" ) ]
      Set Variable [ $error; Value:ErrorScriptMaster ( $result ; "" ) ]
      # use BaseElements plugin
      If [ not ErrorFound ( $error ) ]
      	Set Variable [ $result; Value:BE_CreateFolder ( Get ( TemporaryPath ) & "test" ) ]
      	Set Variable [ $error; Value:ErrorBaseElements ( $result ; "" ) ]
      End If
      # test for FileMaker error
      If [ not ErrorFound ( $error ) ]
      	Set Field [ ScriptLog::notes ; "test" ]
      	Set Variable [ $error; Value:ErrorGetLast ( "" ) ]
      End If
      # handle error
      If [ ErrorFound ( $error ) ]
      	Show Custom Dialog[ Title: "Error"; Message: "An error occured: " & #Get ( $error ; 	"errorDescription" ); Default Button: "OK",
      	Commit: "Yes" ; Button 2: "Cancel", Commit: "No" ]
      	Perform Script [ "Create Script Log Entry ( logData )" ; Parameter: # ( "logData" ; LogData ( "error" ) & $error ) ]
      End If

      The power of these custom functions is that they unify the format of error information, regardless of the source. It makes it easy to test for an error and to access the code/description of that error.

      Without these custom functions, you have to remember how to test for an error from each source. ScriptMaster returns the text "ERROR", BaseElements return value varies, but you can use BE_GetLastError, but then you have to remember if it returns a numeric result or text. Yet another plugin may return "success" or "fail", an xml web service returns a soap fault as xml, and the list goes on.

      1. Hey Dan,

        I can see the validity to the case you're making and I'm now understanding what the outlined intention was. This was not clear to me at the outset however. In fact, this brings up a point which we should likely address.

        The point is this.

        When other developers decide to integrate the functionality we document here, they are making somewhat of a mental agreement to use the functions based on a sense of authoritative trust. This means that I trust that what Jeremy and you have done with # that I can just plug-n-play and I won't "mess" with the functions. Even if I don't take the time to fully understand the code, I know I can just use it because I trust you guys are smart enough to do the smart thing.

        If updates are made, then I would assume that most developers assume they can just copy/paste in the new versions. I made this assumption with ErrorApp. My fault.

        The way the proposed Error system is getting flushed out means that ErrorApp IS the very function I'm talking about. However, we need to document this WITHIN the function. Opening the CF and seeing the current list of "No error", "User canceled action", etc. is misleading in that a developer may assume that there is some implied meaning - when there isn't - unless we all started to agree on some base set of Application specific error codes. Does everyone want to use -1 to mean # didn't parse successfully? I don't know. 

        Given the range of development skill and knowing that not all FileMaker developers are going to use BaseElements, Troi, ScriptMaster, what have you. We need to identify, within CFs, that they are considered quasi-immutable vs. not. Meaning you typically don't want to "mess" with some, but others you do.

        Which reminds me. You'll definitely want to put link backs to the Error handling docs here within the CFs themselves. This will help address this type of confusion.

        I'm sorry I didn't have more understanding about the outlined structure, but this is where we need to use the main page to document this. Then, you could have easily said, "Did you read the proposal specs Matt?" (wink)

         

        1. Agreed, and it looks like I have some documentation to write.

          I think we have agreed on the names of these functions, if someone disagrees, please say so:

          • core functions
            • Error
            • ErrorFmp
            • ErrorFound
            • ErrorGetLast
          • user-defined functions (examples)
            • ErrorApp
            • ErrorScriptMaster
            • ErrorBaseElements
            • etc.

          I'll update the main content of this page with the revised function names/definitions/documentation.

          1. I gave you a head start and I liked the break down between core versus user-defined. Please make any adjustments you see fit within the main page.

            I would like to say that ErrorApp is 'reserved' and HIGHLY suggested if there are no objections.

            1. Regarding the suggestion of using "function" as an error type. How do you suggest implementing that within a custom function?

              The way I see it, there are two options:

              1. save the error data to a $variable
              2. return the error data

              If a custom function was to save the error data to a variable like $error, the variable name must be standardized across all custom functions, otherwise it would be a headache to use. This method also has the potential to modify the contents of a variable already in use, and which the developer did not intend to be modified by the function (in the case of a developer just expecting it to work and not worrying too much about it's inner workings, or reading it's documentation).

              Returning the error data may not always be appropriate, depending on the custom function. A function that is expected to return a boolean value, for example, probably shouldn't return a list of name/value pairs.

              I'm updating this page and will remove the "function" error type for now. It can be added again after it's use is discussed more.

              1. In my use of the custom function CustomList, I had it return its own errors into a local variable. I would then check for the existence of said variable and if it existed I would then handle the error.

                Essentially, I would envision standardizing on a reserved variable for use within CF's. The error handling functions would account for this. Something like $functionError?

  23. Regarding the levels defined in the LogData ( logLevel ) function:

    • info
    • warn
    • error

    I originally planned to have more levels, but I opted to keep it simple instead. I know many other applications have 5 or more log levels. After using this function for a year, I use "error" 99% of the time, "info" 1% and never used "warn".

    Does anyone think we should add two more log levels?

    • critical
    • debug
  24. Would it make more sense for the ErrorGetLast function to be named ErrorGetCode, or even ErrorCode? (I'm increasingly partial to nouns describing the returned results as custom function names these days.) As described, it doesn't necessarily have anything to do with the last error, and everything to do with the error code from a given error "package" or "dictionary" or "struct" or whatever the kids are calling it now.

    1. I haven't really reviewed the updated documentation on this page yet; that's still on my to-do list. However; I just modified the description of ErrorGetLast, which will hopefully clarify things for you. Previously, the documentation didn't match the output of the function.

  25. I've been modifying this page recently, trying to reflect recent comments/suggestions and the current set of functions.

    Here are a few changes I made that weren't discussed much (or at all):

    • Additional namespacing within function names:
      Since we namespaced this set of functions with "Function", I decided it was best to continue the user of namespacing. So ErrorGetLast became ErrorFmpGetLast, to reflect the fact that it's getting the last FileMaker error. ErrorScriptMaster became ErrorPluginScriptMaster, to reflect the fact that it's a Plugin error handling function.
    • Added namespaces to errorType:
      Instead of just using "ScriptMaster" as an error type, I changed it to "Plugin: ScriptMaster". Again, this is to follow through with the use of namespacing throughout these functions. In addition to that, it allows your code to be able to determine the general source of an error: was it from FileMaker, my App, any plugin, any custom function? If the errorType did not contain the text "plugin", it would not be easy to determine that the source of the error was a plugin.
    • Upper Camel Case errorType:
      In order to have the name of the custom function correspond as closely as possible to the value stored in errorType, I've changed fmp to Fmp, app to App, etc.
    • Code 10000 = "plugin not installed":
      I modified the ErrorPluginScriptMaster function to use code 10000 if the plugin is not installed. I think it would be worthwhile trying to create a standard error code for this, that could be used by all plugins. I choose 10000 because FileMaker error codes don't go that high and some people may want to use error codes that are similar to FileMaker's own error codes. If anyone has an alternate suggestion for a standard error code, please speak up.

    I've been working on/using these functions for the last year, so it's difficult for me to have a fresh perspective of them at this point. I've written the documentation as best I can, but I would welcome input from those of you who are new to this: does the documentation give you the information you need to understand how to use these functions? If not, please let me know.

    I now consider these functions complete, and ready to be submitted to the fmpstandards repo. If there are no objections within the next few days, I will submit a pull request.