#What is Logger?
Logger is a PL/SQL logging and debugging framework. The goal of logger is to be as simple as possible to install and use. The primary use cases for this utility include:
- Debugging: It's often difficult to track down the source of an error without some form of debugging instrumentation. This is particularly true in multi-tier, stateless architectures such as Application Express.
- Error Logging: While most experts agree that it's important not to mask errors, it's also nice to have a persistent record of them.
- Timing: Logger has a very simple timing framework built-in that makes it easy to benchmark sections of code.
- Instrumentation: Because it's easy to "turn-off" logger globally with virtually no performance impact, it's easy to get in the habit of leaving debug calls in production code. Now, when something does go wrong, you simply flip the switch and logger is enabled making it much quicker to debug errors.
##Feedback/Issues Please submit any feedback, suggestions, or issues on the project's issue page.
top #Installation
If you're new to Logger it's recommended you simply install into an existing schema on a development environment to get up and running as quickly as possible. You are encouraged to review the rest of the installation sections after you're more familiar with Logger. Once installed go to How to use Logger to get a quick tutorial. Once you are comfortable using Logger it is recommended that you read the Best Practices section
##Important Notes
###Previous Installations Version 2.0.0 build scripts were completely re-written to make it easier for future development. The new build scripts were built off Logger 1.4.0. As such, if your current version is lower than 1.4.0 you need to run the uninstall script for your specific version. If you're currently 1.4.0 or above the installation script will automatically update your current version. The following query will identify your current version.
select pref_value
from logger_prefs
where pref_name = 'LOGGER_VERSION';To uninstall an older version of logger, see the Uninstall instructions. If necessary, you can download the correct version from the releases folder.
###Install Through APEX Logger is no longer supported from a web-only installation if the schema was provisioned by APEX. Essentially the APEX team removed the "create any context" privilege when provisioning a new workspace, likely for security reasons. I agree with their choice, it unfortunately impacts logger.
##Install into a new schema
-
Using sql*plus or SQL Developer, connect to the database as system or a user with the DBA role.
-
Run:
@create_user.sql
1. Enter the username, tablespace, temporary tablespace and password for the new schema.
1. Connect to the database as the newly created user.
1. Follow the steps to install into an existing schema (below)
<a name="install-into-existing-schema"></a>
##Install into an existing schema:
1. If possible, connect as a privileged user and issue the following grants to your "existing_user":
```sql
grant connect,create view, create job, create table, create sequence,
create trigger, create procedure, create any context to existing_user;
- Run:
@logger_install.sql
1. Once installed, Logger is automatically set to **DEBUG** level. View the [configurations](#configuration) section to modify its settings.
<a name="intsall-no-op"></a>
##NO-OP Option for Production Environments
To make sure there is no fear of leaving debug statements in production code, Logger comes with a [NO-OP](http://en.wikipedia.org/wiki/NOP) (No Operation) installation file (logger_no_op.sql). This installs only a shell of the Logger package. All procedures are essentially NO-OPs. It does not even create the tables so there is absolutely no chance it is doing any logging. It is recommended that you leave the full version installed and simply dial-down the TOOD LINK level to "ERROR" as the performance hit is exceptionally small.
##Objects
The following database objects are installed with Logger:
```sql
OBJECT_TYPE OBJECT_NAME
------------------- ------------------------------
JOB LOGGER_PURGE_JOB
LOGGER_UNSET_PREFS_BY_CLIENT
PACKAGE LOGGER
PROCEDURE LOGGER_CONFIGURE
SEQUENCE LOGGER_APX_ITEMS_SEQ
LOGGER_LOGS_SEQ
TABLE LOGGER_LOGS
LOGGER_LOGS_APEX_ITEMS
LOGGER_PREFS
LOGGER_PREFS_BY_CLIENT_ID
VIEW LOGGER_LOGS_5_MIN
LOGGER_LOGS_60_MIN
LOGGER_LOGS_TERSE
LOGGER_GLOBAL_CTX CONTEXT -- Global Application Contexts are owned by SYS
##Uninstall To uninstall Logger simple run the following script in the schema that Logger was installed in:
@drop_logger.sqlg_permanent constant number := 1;
g_error constant number := 2;
g_warning constant number := 4;
g_information constant number := 8;
g_debug constant number := 16;
-- Any level > debug (16) is enabled when the LEVEL is set to DEBUG, but you cannot explicitly set the LEVEL to say TIMING.
-- The additional levels are there for reporting purposes only.
g_timing constant number := 32;
g_sys_context constant number := 64;
g_apex constant number := 128;###Enable To enable logging for the entire schema:
exec logger.set_level('DEBUG');###Disable To disable logging:
exec logger.set_level('OFF');Instead of disabling all logging, setting the level to "ERROR" might be a better approach:
exec logger.set_level('ERROR');If you never want logger to run in an environment you can install the NO-OP version.
###Client Specific Configuration Prior to version 2.0.0 Logger only supported one logger level. The primary goal of this approach was to enable Logger at Debug level for development environments, then change it to Error levels in production environments so the logs did not slow down the system. Over time developers start to find that in some situations they needed to see what a particular user / session was doing in production. Their only option was to enable Logger for the entire system which could potentially slow everyone down.
Starting in version 2.0.0 you can now specify the logger level along with call stack setting by specifying the client_identifier. If not explicitly unset, client specific configurations will expire after a set period of time. The following example highlights how this works:
-- In Oracle Session-1
exec logger.set_level('DEBUG');
exec logger.log('Session-1: this should show up');
select id, logger_level, text, client_identifier, call_stack
from logger_logs_5_min
order by id;
ID LOGGER_LEVEL TEXT CLIENT_IDENTIFIER CALL_STACK
---- ------------ ----------------------------------- ----------------- ----------------------------
31 16 Session-1: this should show up object line object
exec logger.set_level ('ERROR');
exec logger.log('Session-1: this should NOT show up');
-- The previous line does not get logged since the logger level is set to ERROR and it made a .log call
-- In Oracle Session-2 (i.e. a different session)
exec dbms_session.set_identifier('my_identifier');
-- This sets the logger level for current identifier
exec logger.set_level('DEBUG', sys_context('userenv','client_identifier'));
exec logger.log('Session-2: this should show up');
select id, logger_level, text, client_identifier, call_stack
from logger_logs_5_min
order by id;
ID LOGGER_LEVEL TEXT CLIENT_IDENTIFIER CALL_STACK
---- ------------ ----------------------------------- ----------------- ----------------------------
31 16 Session-1: this should show up object line object
32 16 Session-2: this should show up my_identifier object line object
-- Notice how the CLIENT_IDENTIFIER field also contains the current client_identiferThe following is the header for the set_level procedure:
procedure set_level(
p_level in varchar2 default 'DEBUG',
p_client_id in varchar2 default null, -- You must specify this to enable client_specific logging
p_include_call_stack in varchar2 default null, -- TRUE or FALSE. Default: TRUE
p_client_id_expire_hours in number default null -- Number of hours after which the client_id specific logging will be expired. Default is stored in logger_prefs: PREF_BY_CLIENT_ID_EXPIRE_HOURS
)####APEX CLIENT_ID In APEX the client_identifier is
:APP_USER || ':' || :APP_SESSION ####Unset Client Specific Logging They're a few options to unset client specific logging:
Unset by specific client_id:
exec logger.unset_client_level('my_client_id');Unset all expired client_ids. Note this run automatically each hour by the LOGGER_UNSET_PREFS_BY_CLIENT job.
exec logger.unset_client_level;Unset all client configurations (regardless of expiry time):
exec logger.unset_client_level_all;####View All Client Specific Configurations The following query shows all the current client specific log configurations:
select *
from logger_prefs_by_client_id;
CLIENT_ID LOGGER_LEVEL INCLUDE_CALL_STACK CREATED_DATE EXPIRY_DATE
------------------- ------------- ------------------ -------------------- --------------------
logger_demo_session ERROR TRUE 24-APR-2013 02:48:13 24-APR-2013 14:48:13###Status To view the status/configuration of the Logger:
set serveroutput on
exec logger.status
Project Home Page : https://github.com/tmuth/Logger---A-PL-SQL-Logging-Utility/
Logger Version : 2.0.0.a01
Debug Level : DEBUG
Capture Call Stack : TRUE
Protect Admin Procedures : TRUE
APEX Tracing : Disabled
SCN Capture : Disabled
Min. Purge Level : DEBUG
Purge Older Than : 7 days
Pref by client_id expire : 12 hours
For all client info see : logger_prefs_by_client_id
PL/SQL procedure successfully completed.###Other Options
Once you perform the following described steps for the Flashback or APEX option, simply run the logger_configure procedure, then run logger.status to check validate your changes.
exec logger_configure;
exec logger.status;####Flashback To enable this option, grant execute on dbms_flashback to the user that owns the logger packages. Every insert into logger_logs will include the SCN (System Commit Number). This allows you to flashback a session to the time when the error occurred to help debug it or even undo any data corruption. As SYS from sql*plus:
grant execute on dbms_flashback to logger;####APEX This option allows you to call logger.log_apex_items which grabs the names and values of all APEX items from the current session and stores them in the logger_logs_apex_items table. This is extremely useful in debugging APEX issues. This option is enabled automatically by logger_configure if APEX is installed in the database.
##Maintenance
By default, the DBMS_SCHEDULER job "LOGGER_PURGE_JOB" runs every night at 1:00am and deletes any logs older than 7 days that are of error level g_debug or higher which includes g_debug and g_timing. This means logs with any lower level such as g_error or g_permanent will never be purged. You can also manually purge all logs using logger.purge_all, but this will not delete logs of error level g_permanent.
Starting in 2.0.0 a new job was LOGGER_UNSET_PREFS_BY_CLIENT introduced to remove client specific logging. By default this job is run every hour on the hour.
The following example is the most basic use of Logger. This use-case will never change for this project as one of the goals is to eliminate any learning curve for a debugging utility.
exec logger.log('hello world');
select * from logger_logs;Logger is based on multiple levels. Calling the following procedures will log the content at each of the levels.
begin
logger.log('This is a debug message. (level = DEBUG)');
logger.log_information('This is an informational message. (level = INFORMATION)');
logger.log_warning('This is a warning message. (level = WARNING)');
logger.log_error('This is an error message (level = ERROR)');
logger.log_permanent('This is a permanent message, good for upgrades and milestones. (level = PERMANENT)');
end;
/
select id, logger_level, text
from logger_logs_5_min
order by id;
ID LOGGER_LEVEL TEXT
---- ------------ ------------------------------------------------------------------------------------------
10 16 This is a debug message. (level = DEBUG)
11 8 This is an informational message. (level = INFORMATION)
12 4 This is a warning message. (level = WARNING)
13 2 This is an error message (level = ERROR)
14 1 This is a permanent message, good for upgrades and milestones. (level = PERMANENT)##Parameters The primary logger procedures have three common parameters: p_text, p_scope, p_extra, and p_params. Each parameter is described below.
###p_text You should always include some message. p_text maps to the text column in logger_logs. As such it should not exceed 4000 characters. If you need to store more text you can use the p_extra parameter.
###p_scope (optional but highly recommend) The idea behind scope is to give some context to the log message, such as the application, package.procedure where it was called. Logger does capture the call stack, as well as module and action which are great for APEX logging as they are app number / page number. However, none of these options gives you a clean, consistent way to group messages. The p_scope parameter is really nothing special as it simply performs a lower() on the input and stores it in the scope column.
The following example demonstrates how to use p_scope when called from an APEX application:
exec logger.log('Some text', 'apex.my_app.page4.some_process');
select id,text,scope from logger_logs where scope like 'apex.my_app.%' order by id;
ID TEXT SCOPE
---- -------------------- ----------------------------------------
3 Some text apex.my_app.page4.some_processSee the Best Practices section for scope standards.
###p_extra (optional) When logging large (over 4000 characters) blocks of text, use the third parameter: p_extra. p_extra is a clob field and thus isn't restricted to the 4000 character limit.
The following example highlights the p_extra usage:
exec logger.log('Some text', 'a.scope', 'Large block of text');
select id, text, scope, extra from logger_logs_5_min;
ID TEXT SCOPE EXTRA
---- ---------- ---------- --------------------
4 Some text a.scope Large block of text
The p_params parameter is for storing the parameters object. The information from the parameters array will be appended to the extra column. See Log Parameters section for an example.
When calling logger.log_error it is highly recommended that you leverage this 4th parameter.
top #Advanced use
##Error Handling
declare
x number;
begin
execute immediate 'select count(*) into x from foo1234';
exception when others then
logger.log_error('Intentional error');
raise;
end;
/
select * from logger_logs where logger_level = 2;
ID LOGGER_LEVEL TEXT CALL_STACK TIME_STAMP APP_SESSION MODULE ACTION USER_NAME UNIT_NAME LINE_NO COMMENTS SCN
-- ------------ -------- ---------- ------------------------ ------------ ------------- ------ ---------- ----------------- -------- --------- ---
2 2 ORA-0094 ORA-06512: 13-OCT-09 09.14.50.07 AM SQL Developer LOGGER INTENTIONAL ERROR 6##Error Handling Showing the Call Stack In this example, procedure test1 calls procedure test2 which in turn class test3. Test3 has a run-time error.
create or replace procedure test3 as
begin
execute immediate 'select count(*) into x from foo1234';
end test3;
/
create or replace procedure test2 as
begin
test3;
end;
/
create or replace procedure test1 as
begin
test2;
exception when others then
logger.log_error();
raise;
end;
/
exec test1;
SQL> select call_stack from logger_logs where id = 4;
CALL_STACK
-----------------------------------------
ORA-00942: table or view does not exist
ORA-06512: at "LOGGER.TEST3", line 5
ORA-06512: at "LOGGER.TEST2", line 5
ORA-06512: at "LOGGER.TEST1", line 4##Timing
declare
l_number number;
begin
logger.time_reset;
logger.time_start('foo');
logger.time_start('bar');
for i in 1..500000 loop
l_number := power(i,15);
l_number := sqrt(1333);
end loop; --i
logger.time_stop('bar');
for i in 1..500000 loop
l_number := power(i,15);
l_number := sqrt(1333);
end loop; --i
logger.time_stop('foo');
end;
/
select text from logger_logs_5_min;
TEXT
---------------------------------
START: foo
> START: bar
> STOP : bar - 1.000843 seconds
STOP : foo - 2.015953 seconds##Log User Environment Variables There are many occasions when the value of one of the USERENV session variables (Documentation: Overview, list of variables) is a big step in the right direction of finding a problem. A simple call to the logger.log_userenv procedure is all it takes to save them in the "EXTRA" column of logger_logs.
logger.log_userenv(
p_detail_level in varchar2 default 'USER',-- ALL, NLS, USER, INSTANCE
p_show_null in boolean default false,
p_scope in varchar2 default null)exec logger.log_userenv('NLS');
select text,extra from logger_logs_5_min;
TEXT EXTRA
---------------------------------------------- -----------------------------------------------------------------
USERENV values stored in the EXTRA column NLS_CALENDAR : GREGORIAN
NLS_CURRENCY : $
NLS_DATE_FORMAT : DD-MON-RR
NLS_DATE_LANGUAGE : AMERICAN
NLS_SORT : BINARY
NLS_TERRITORY : AMERICA
LANG : US
LANGUAGE : AMERICAN_AMERICA.WE8MSWIN1252exec logger.log_userenv('USER');
select text,extra from logger_logs_5_min;
TEXT EXTRA
-------------------------------------------------- -------------------------------------------------------
USERENV values stored in the EXTRA column CURRENT_SCHEMA : LOGGER
SESSION_USER : LOGGER
OS_USER : tmuth
IP_ADDRESS : 192.168.1.7
HOST : WORKGROUP\TMUTH-LAP
TERMINAL : TMUTH-LAP
AUTHENTICATED_IDENTITY : logger
AUTHENTICATION_METHOD : PASSWORD##Log OWA_UTL CGI Environment Variables
This option only works within a web session, but it's a great way to quickly take a look at an APEX environment:
SQL> exec logger.log_cgi_env;
select extra from logger_logs where text like '%CGI%';
TEXT EXTRA
-------------------------------------------------- -------------------------------------------------------
...
SERVER_SOFTWARE : Oracle-Application-Server-10g/10.1.3.1.0 Oracle-HTTP-Server
GATEWAY_INTERFACE : CGI/1.1
SERVER_PORT : 80
SERVER_NAME : 11g
REQUEST_METHOD : POST
PATH_INFO : /wwv_flow.show
SCRIPT_NAME : /pls/apex
REMOTE_ADDR : 192.168.1.7
...##Log Character Codes
Have you ever run into an issue with a string that contains control characters such as carriage returns, line feeds and tabs that are difficult to debug? The sql dump() function is great for this, but the output is a bit hard to read as it outputs the character codes for each character, so you end up comparing the character code to an ascii table to figure out what it is. The function get_character_codes and the procedure log_character_codes make it much easier as they line up the characters in the original string under the corresponding character codes from dump. Additionally, all tabs are replaced with "^" and all other control characters such as carriage returns and line feeds are replaced with "~".
exec logger.log_character_codes('Hello World'||chr(9)||'Foo'||chr(13)||chr(10)||'Bar');
select extra from logger_logs_5_min;
EXTRA
----------------------------------------------------------------------------------
Common Codes: 13=Line Feed, 10=Carriage Return, 32=Space, 9=Tab
72,101,108,108,111, 32, 87,111,114,108,100, 9, 70,111,111, 13, 10, 66, 97,114
H, e, l, l, o, , W, o, r, l, d, ^, F, o, o, ~, ~, B, a, r##Log APEX Item Values This feature is useful in debugging issues in an APEX application that are related session state. The developers toolbar in APEX provides a place to view session state, but it won't tell you the value of items midway through page rendering or right before and after an AJAX call to an application process.
Before using this feature it's important to note that it must be configured first. The next section discusses this configuration.
-- in an on-submit page process
begin
logger.log_apex_items('Debug Edit Customer');
end;select id,logger_level,text,module,action,client_identifier from logger_logs where logger_level = 128;
ID LOGGER_LEVEL TEXT MODULE ACTION CLIENT_IDENTIFIER
------- ------------ -------------------- ---------------------- --------- --------------------
47 128 Debug Edit Customer APEX:APPLICATION 100 PAGE 7 ADMIN:45588554040361
select * from logger_logs_apex_items where log_id = 47; --log_id relates to logger_logs.id
ID LOG_ID APP_SESSION ITEM_NAME ITEM_VALUE
------- ------- ---------------- ------------------------- ---------------------------------------------
136 47 45588554040361 P1_QUOTA
137 47 45588554040361 P1_TOTAL_SALES
138 47 45588554040361 P6_PRODUCT_NAME 3.2 GHz Desktop PC
139 47 45588554040361 P6_PRODUCT_DESCRIPTION All the options, this machine is loaded!
140 47 45588554040361 P6_CATEGORY Computer
141 47 45588554040361 P6_PRODUCT_AVAIL Y
142 47 45588554040361 P6_LIST_PRICE 1200
143 47 45588554040361 P6_PRODUCT_IMAGE
144 47 45588554040361 P4_CALENDAR_DATE 20091103
145 47 45588554040361 P7_CUSTOMER_ID 6
146 47 45588554040361 P7_BRANCH 2
147 47 45588554040361 P29_ORDER_ID_NEXT
148 47 45588554040361 P29_ORDER_ID_PREV
149 47 45588554040361 P29_ORDER_ID_COUNT 0 of 0
150 47 45588554040361 P7_CUST_FIRST_NAME Albert
151 47 45588554040361 P7_CUST_LAST_NAME Lambert
152 47 45588554040361 P7_CUST_STREET_ADDRESS1 10701 Lambert International Blvd.
153 47 45588554040361 P7_CUST_STREET_ADDRESS2
154 47 45588554040361 P7_CUST_CITY St. Louis
155 47 45588554040361 P7_CUST_STATE MO
156 47 45588554040361 P7_CUST_POSTAL_CODE 63145
157 47 45588554040361 P7_CUST_EMAIL
158 47 45588554040361 P7_PHONE_NUMBER1 314-555-4022
159 47 45588554040361 P7_PHONE_NUMBER2
160 47 45588554040361 P7_CREDIT_LIMIT 1000
161 47 45588554040361 P6_PRODUCT_ID 1
162 47 45588554040361 P29_ORDER_ID 9
##Log Parameters Logger has wrapper functions to quickly and easily log parameters. All primary log procedures take in a fourth parameter to support logging a parameter array. The values are explicitly converted to strings so you don't need to convert them. The parameter values will be stored n the extra column.
The following example highlights how to use the log parameter wrappers:
create or replace procedure p_demo_function(
p_empno in emp.empno%type,
p_ename in emp.ename%type)
as
l_scope logger_logs.scope%type := 'p_demo_function';
l_params logger.tab_param;
begin
logger.append_param(l_params, 'p_empno', p_empno); -- Parameter name and value just stored in PL/SQL array and not logged yet
logger.append_param(l_params, 'p_ename', p_ename); -- Parameter name and value just stored in PL/SQL array and not logged yet
logger.log('START', l_scope, null, l_params); -- All parameters are logged at this point
-- ...
exception
when others then
logger.log_error('Unhandled Exception', l_scope, null, l_params);
end p_demo_function;
/Parameters can also be passed in as the last (4th) parameter in the logger.log_error procedure. This is useful in production instances where the Logger level is usually set to ERROR. When an error occurs parameters will be logged in the extra column.
##PL/SQL Procedure / Function
For packages the recommended practice is as follows:
create or replace package body pkg_example
as
gc_scope_prefix constant VARCHAR2(31) := lower($$PLSQL_UNIT) || '.';
procedure demo_proc(
p_param1 in varchar2)
as
l_scope logger_logs.scope%type := gc_scope_prefix || 'demo_proc'; -- Use the function or procedure name
l_params logger.tab_param;
begin
logger.append_param(l_params, 'p_param1', p_param1);
logger.log('START', l_scope, null, l_params);
...
-- All calls to logger should pass in the scope
...
logger.log('END', l_scope);
exception
when others then
logger.log_error('Unhandled Exception', l_scope, null, l_params);
end demo proc;
...top #Change Log ##Version 2.0.0
- Moved to GitHub and restructured / updated documentation
- Added p_params support and append_params function
- Client specific level setting (to enable logging based on client_id)
- New build script which will allow for future versions of logger to be updated. This was built off a 1.4.0 release.
- Fixed an issue detecting 11.2 RAC installations
- APEX no longer supported from a web-only installation if the schema was provisioned by APEX. Essentially the APEX team removed the "create any context" privelege when provisioning a new workspace, likely for security reasons. I (Tyler) agree with their choice, it unfortunately impacts logger.
- Fixed major flaw in time calculation used in time_start/time_stop
- Changed implementation of LOG_APEX_ITEMS to use the APEX views so explicit privs on wwv_flow_data are not required. Thanks to Scott Spendolini for this suggestion.
- Fixed an error with the admin security check reported by John Flack
- It is now possible to install logger in multiple schemas as the global context is now prefixed with the schema name. So, the global context name in the LOGGER schema would be LOGGER_LOGCTX and the SCOTT schema would be SCOTT_LOGCTX. Thanks to Bill Wheeling for reporting this one.
- New: PROTECT_ADMIN_PROCS preference which is TRUE by default, protects set_level, purge and purge_all. This means that only someone logged into the schema where logger is installed can call these procedures. The idea is that you could grant execute on logger to other schemas, but want to prevent them from changing the levels or purging the logs.
- New: preference called INCLUDE_CALL_STACK allows you to enable / disable logging of the full call stack for LEVELS greater than ERROR (such as debug). Logging the call stack does take additional resources and also requires additional storage per row. So, you can still read your debug messages, but you simply won't see the full call stack. The value is TRUE by default.
- New: CLOB parameter of "P_EXTRA" was added to call LOG... procedures. This populates a CLOB column in LOGGER_LOGS called "EXTRA". This column is also used by several new functions / procedures where the values are relatively large.
- New: logger.log_userenv procedure logs information obtained through sys_context('userenv'...), such as IP Address, NLS info, schema / user information. It's use is documented here.
- New: logger.log_cgi_env procedure grabs all output from owa_util.print_cgi_env and logs it to logger_logs.extra. Useful in debugging some APEX issues. It's use is documented here.
- New: logger.log_character_codes procedure supplements the output of the SQL DUMP() function, great for finding hidden carriage return / line feeds or other non-printable characters.It's use is documented here.
- Fixed set_level, purge and purge_all so they are now autonomous transactions (thanks Tony).
#License
Copyright (c) 2013, Tyler D. Muth, tylermuth.wordpress.com and contributors to the project at https://github.com/tmuth/Logger---A-PL-SQL-Logging-Utility All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of Tyler D Muth, nor Oracle Corporation, nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.